wasm_metadata/
registry.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
use anyhow::Result;
use serde_derive::{Deserialize, Serialize};
use spdx::Expression;
use std::fmt;
use std::fmt::Display;
use wasmparser::Parser;

use crate::{rewrite_wasm, Producers};

/// Metadata used by a Warg registry
#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq)]
pub struct RegistryMetadata {
    /// List of authors who has created this package.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub authors: Option<Vec<String>>,

    /// Package description in markdown format.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,

    /// SPDX License Expression
    /// <https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/>
    /// SPDX License List: <https://spdx.org/licenses/>
    #[serde(skip_serializing_if = "Option::is_none")]
    pub license: Option<String>,

    /// A list of custom licenses that should be referenced to from the license expression.
    /// <https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/>
    #[serde(skip_serializing_if = "Option::is_none")]
    pub custom_licenses: Option<Vec<CustomLicense>>,

    /// A list of links that can contain predefined link types or custom links for use with tooling or registries.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub links: Option<Vec<Link>>,

    /// A list of categories that a package should be listed under when uploaded to a registry.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub categories: Option<Vec<String>>,
}

const LICENSE_REF: &str = "LicenseRef-";

impl RegistryMetadata {
    /// Merge into an existing wasm module. Rewrites the module with this registry-metadata section
    /// overwriting its existing one, or adds this registry-metadata section if none is present.
    pub fn add_to_wasm(&self, input: &[u8]) -> Result<Vec<u8>> {
        rewrite_wasm(&None, &Producers::empty(), Some(&self), input)
    }

    /// Parse a Wasm binary and extract the `Registry` section, if there is any.
    pub fn from_wasm(bytes: &[u8]) -> Result<Option<Self>> {
        let mut depth = 0;
        for payload in Parser::new(0).parse_all(bytes) {
            let payload = payload?;
            use wasmparser::Payload::*;
            match payload {
                ModuleSection { .. } | ComponentSection { .. } => depth += 1,
                End { .. } => depth -= 1,
                CustomSection(c) if c.name() == "registry-metadata" && depth == 0 => {
                    let registry = RegistryMetadata::from_bytes(&c.data(), 0)?;
                    return Ok(Some(registry));
                }
                _ => {}
            }
        }
        Ok(None)
    }

    /// Gets the registry-matadata from a slice of bytes
    pub fn from_bytes(bytes: &[u8], offset: usize) -> Result<Self> {
        let registry: RegistryMetadata = serde_json::from_slice(&bytes[offset..])?;
        return Ok(registry);
    }

    /// Validate the `RegistryMetadata::license` an
    /// `RegistryMetadata::CustomLicense` are valid SPDX expressions.
    pub fn validate(&self) -> Result<()> {
        fn validate_expression(expression: &str) -> Result<Vec<String>> {
            let expression = Expression::parse(expression)?;

            let mut licenses = Vec::new();

            for license in expression.iter() {
                match license {
                    spdx::expression::ExprNode::Op(_) => continue,
                    spdx::expression::ExprNode::Req(req) => {
                        if let spdx::LicenseItem::Spdx { .. } = req.req.license {
                            // Continue if it's a license that exists on the Spdx license list
                            continue;
                        }

                        let license_id = req.req.to_string();

                        // Strip "LicenseRef-", convert to lowercase and then append
                        if let Some(id) = license_id.strip_prefix(LICENSE_REF) {
                            licenses.push(id.to_lowercase());
                        }
                    }
                }
            }

            Ok(licenses)
        }

        match (&self.license, &self.custom_licenses) {
            (None, Some(custom_licenses)) => {
                let ids = custom_licenses
                    .iter()
                    .map(|license| license.id.clone())
                    .collect::<Vec<String>>()
                    .join(", ");

                return Err(anyhow::anyhow!(
                    "{ids} are defined but nevered referenced in license expression"
                ));
            }
            (Some(license), Some(custom_licenses)) => {
                let licenses = validate_expression(license.as_str())?;

                if !licenses.is_empty() {
                    for license in &licenses {
                        let mut match_found = false;
                        for custom_license in custom_licenses {
                            // Ignore license id casing
                            if custom_license.id.to_lowercase() == *license {
                                match_found = true;
                            }
                        }

                        if !match_found {
                            return Err(anyhow::anyhow!(
                                "No matching reference for license '{license}' was defined"
                            ));
                        }
                    }
                }
            }
            (Some(license), None) => {
                let licenses = validate_expression(license.as_str())?;

                if !licenses.is_empty() {
                    return Err(anyhow::anyhow!(
                        "Reference to custom license exists but no custom license was given"
                    ));
                }
            }
            (None, None) => {}
        }

        Ok(())
    }

    /// Get authors
    pub fn get_authors(&self) -> Option<&Vec<String>> {
        self.authors.as_ref()
    }

    /// Set authors
    pub fn set_authors(&mut self, authors: Option<Vec<String>>) {
        self.authors = authors;
    }

    /// Get description
    pub fn get_description(&self) -> Option<&String> {
        self.description.as_ref()
    }

    /// Set description
    pub fn set_description(&mut self, description: Option<String>) {
        self.description = description;
    }

    /// Get license
    pub fn get_license(&self) -> Option<&String> {
        self.license.as_ref()
    }

    /// Set license
    pub fn set_license(&mut self, license: Option<String>) {
        self.license = license;
    }

    /// Get custom_licenses
    pub fn get_custom_licenses(&self) -> Option<&Vec<CustomLicense>> {
        self.custom_licenses.as_ref()
    }

    /// Set custom_licenses
    pub fn set_custom_licenses(&mut self, custom_licenses: Option<Vec<CustomLicense>>) {
        self.custom_licenses = custom_licenses;
    }

    /// Get links
    pub fn get_links(&self) -> Option<&Vec<Link>> {
        self.links.as_ref()
    }

    /// Set links
    pub fn set_links(&mut self, links: Option<Vec<Link>>) {
        self.links = links;
    }

    /// Get categories
    pub fn get_categories(&self) -> Option<&Vec<String>> {
        self.categories.as_ref()
    }

    /// Set categories
    pub fn set_categories(&mut self, categories: Option<Vec<String>>) {
        self.categories = categories;
    }

    pub(crate) fn display(&self, f: &mut fmt::Formatter, indent: usize) -> fmt::Result {
        let spaces = std::iter::repeat(" ").take(indent).collect::<String>();

        if let Some(authors) = &self.authors {
            writeln!(f, "{spaces}authors:")?;
            for author in authors {
                writeln!(f, "{spaces}    {author}")?;
            }
        }

        if let Some(license) = &self.license {
            writeln!(f, "{spaces}license:")?;
            writeln!(f, "{spaces}    {license}")?;
        }

        if let Some(links) = &self.links {
            writeln!(f, "{spaces}links:")?;
            for link in links {
                writeln!(f, "{spaces}    {link}")?;
            }
        }

        if let Some(categories) = &self.categories {
            writeln!(f, "{spaces}categories:")?;
            for category in categories {
                writeln!(f, "{spaces}    {category}")?;
            }
        }

        if let Some(description) = &self.description {
            writeln!(f, "{spaces}description:")?;
            writeln!(f, "{spaces}    {description}")?;
        }

        if let Some(custom_licenses) = &self.custom_licenses {
            writeln!(f, "{spaces}custom_licenses:")?;
            for license in custom_licenses {
                license.display(f, indent + 4)?;
            }
        }

        Ok(())
    }
}

impl Display for RegistryMetadata {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.display(f, 0)
    }
}

/// A link from the registry to an external website
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct Link {
    /// The type of link
    pub ty: LinkType,
    /// The address of the link
    pub value: String,
}

impl Display for Link {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}: {}", self.ty, self.value)
    }
}

/// What kind of link is this?
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum LinkType {
    /// Documentation
    Documentation,
    /// Homepage
    Homepage,
    /// Code repository
    Repository,
    /// Funding and donations
    Funding,
    /// Some other link type
    #[serde(untagged)]
    Custom(String),
}

impl Display for LinkType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match self {
            LinkType::Documentation => "Documentation",
            LinkType::Homepage => "Homepage",
            LinkType::Repository => "Repository",
            LinkType::Funding => "Funding",
            LinkType::Custom(s) => s.as_str(),
        };

        write!(f, "{s}")
    }
}

/// A human readable short form license identifier for a license not on the SPDX
/// License List.
#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq)]
pub struct CustomLicense {
    /// License Identifier
    /// Provides a locally unique identifier to refer to licenses that are not found on the SPDX License List.
    /// <https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#101-license-identifier-field>
    pub id: String,

    /// License Name
    /// Provide a common name of the license that is not on the SPDX list.
    /// <https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#103-license-name-field>
    pub name: String,

    /// Extracted Text
    /// Provides a copy of the actual text of the license reference extracted from the package or file that is associated with the License Identifier to aid in future analysis.
    /// <https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#102-extracted-text-field>
    pub text: String,

    /// License Cross Reference
    /// Provides a pointer to the official source of a license that is not included in the SPDX License List, that is referenced by the License Identifier.
    /// <https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#104-license-cross-reference-field>
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reference: Option<String>,
}

impl CustomLicense {
    fn display(&self, f: &mut fmt::Formatter, indent: usize) -> fmt::Result {
        let spaces = std::iter::repeat(" ").take(indent).collect::<String>();

        writeln!(f, "{spaces}{}:", self.id)?;
        writeln!(f, "{spaces}    name: {}", self.name)?;

        if let Some(reference) = &self.reference {
            writeln!(f, "{spaces}    reference: {reference}")?;
        }

        writeln!(f, "{spaces}    text:")?;
        writeln!(f, "{spaces}        {}", self.text)?;

        Ok(())
    }
}

impl Display for CustomLicense {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.display(f, 0)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::Metadata;
    use wasm_encoder::Module;

    #[test]
    fn overwrite_registry_metadata() {
        let module = Module::new().finish();
        let registry_metadata = RegistryMetadata {
            authors: Some(vec!["Foo".to_owned()]),
            ..Default::default()
        };
        let module = registry_metadata.add_to_wasm(&module).unwrap();

        let registry_metadata = RegistryMetadata {
            authors: Some(vec!["Bar".to_owned()]),
            ..Default::default()
        };
        let module = registry_metadata.add_to_wasm(&module).unwrap();

        let metadata = Metadata::from_binary(&module).unwrap();
        match metadata {
            Metadata::Module {
                registry_metadata, ..
            } => {
                let registry_metadata = registry_metadata.expect("some registry_metadata");
                assert_eq!(registry_metadata.authors.unwrap(), vec!["Bar".to_owned()]);
            }
            _ => panic!("metadata should be module"),
        }
    }
}