audio-plugin-bsd 0.1.1

Dynamic .so audio-plugin loader with ABI verification and FreeBSD Capsicum/pdfork per-process sandboxing for real-time audio in Rust
Documentation
//! Static metadata describing a loaded plugin (name, version, description, ...).

/// Human-readable metadata for an audio plugin.
///
/// A plugin exposes this — via an `extern "C"` accessor in the full loader —
/// so the host can log plugin identity without interpreting plugin internals.
/// The `abi_version` field records which host ABI the plugin was built
/// against (see [`crate::abi`]).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginMetadata {
    /// Short human-readable name of the plugin (e.g. `"reverb-plate"`).
    pub name: String,
    /// Plugin version string (typically semver, e.g. `"0.3.1"`); free-form.
    pub version: String,
    /// One-line description of what the plugin does.
    pub description: String,
    /// Optional author / vendor string.
    pub author: Option<String>,
    /// Encoded ABI version this plugin was built against (see [`crate::abi`]).
    pub abi_version: u32,
    /// Optional SPDX licence identifier (e.g. `"MIT"`).
    pub license: Option<String>,
}

impl PluginMetadata {
    /// Construct a [`PluginMetadata`] with the required fields, leaving
    /// `author` and `license` as `None`.
    #[must_use]
    pub fn new(
        name: impl Into<String>,
        version: impl Into<String>,
        description: impl Into<String>,
        abi_version: u32,
    ) -> Self {
        Self {
            name: name.into(),
            version: version.into(),
            description: description.into(),
            author: None,
            abi_version,
            license: None,
        }
    }

    /// Builder: set the `author` field.
    #[must_use]
    pub fn with_author(mut self, author: impl Into<String>) -> Self {
        self.author = Some(author.into());
        self
    }

    /// Builder: set the `license` field.
    #[must_use]
    pub fn with_license(mut self, license: impl Into<String>) -> Self {
        self.license = Some(license.into());
        self
    }
}

#[cfg(test)]
mod tests {
    use crate::abi::encode_abi_version;

    use super::PluginMetadata;

    #[test]
    fn new_sets_required_fields_and_defaults_optionals() {
        let m = PluginMetadata::new("gain", "0.1.0", "simple gain", encode_abi_version(1, 0));
        assert_eq!(m.name, "gain");
        assert_eq!(m.version, "0.1.0");
        assert_eq!(m.description, "simple gain");
        assert_eq!(m.author, None);
        assert_eq!(m.license, None);
        assert_eq!(m.abi_version, encode_abi_version(1, 0));
    }

    #[test]
    fn with_author_sets_author() {
        let m = PluginMetadata::new("gain", "0.1.0", "gain", 0).with_author("acme-audio");
        assert_eq!(m.author.as_deref(), Some("acme-audio"));
    }

    #[test]
    fn with_license_sets_license() {
        let m = PluginMetadata::new("gain", "0.1.0", "gain", 0).with_license("MIT");
        assert_eq!(m.license.as_deref(), Some("MIT"));
    }

    #[test]
    fn builders_chain() {
        let m = PluginMetadata::new("reverb", "1.2.3", "plate reverb", 1)
            .with_author("alice")
            .with_license("BSD-2-Clause");
        assert_eq!(m.name, "reverb");
        assert_eq!(m.author.as_deref(), Some("alice"));
        assert_eq!(m.license.as_deref(), Some("BSD-2-Clause"));
    }

    #[test]
    fn eq_clone_hold() {
        let a = PluginMetadata::new("g", "1", "d", 0);
        assert_eq!(a.clone(), a);
        let b = PluginMetadata::new("g", "1", "d", 0);
        assert_eq!(a, b);
        assert_ne!(a, PluginMetadata::new("g2", "1", "d", 0));
    }

    #[test]
    fn debug_includes_fields() {
        let m = PluginMetadata::new("gain", "0.1", "g", 1);
        let s = format!("{m:?}");
        assert!(s.contains("PluginMetadata"));
        assert!(s.contains("\"gain\""));
    }
}