Skip to content

How-To: Create a Package Metadata Schema

This guide shows how to define a host-side schema type for validating the [metadata] section of package.toml manifests.

Problem

You are building a host application that loads source packages. You want to enforce that every package provides specific metadata fields (e.g., category, min_host_version) and reject packages that omit required fields or use incorrect types.

Solution

Define a Rust struct that derives serde::Deserialize, then pass it as the type parameter to load_package_manifest::<MySchema>().

Step 1: Define the schema struct

use serde::Deserialize;

/// Host-defined metadata schema for plugin packages.
///
/// Every package.toml [metadata] section must deserialize into this type.
/// Missing required fields or type mismatches cause a `PackageError::ParseError`.
#[derive(Debug, Deserialize)]
pub struct PluginMetadata {
    /// Required: plugin category for the host's catalog UI.
    pub category: String,

    /// Required: minimum host version this plugin supports.
    pub min_host_version: String,

    /// Optional: human-readable description.
    #[serde(default)]
    pub description: Option<String>,

    /// Optional: list of tags for search/filtering.
    #[serde(default)]
    pub tags: Vec<String>,
}

Fields without #[serde(default)] are required — if the TOML does not contain them, parsing fails with PackageError::ParseError. Fields with #[serde(default)] or Option<T> are optional.

Extra fields in the TOML that are not present in the struct are silently ignored by serde's default behavior.

Step 2: Write the matching TOML

In package.toml:

[package]
name = "blur-filter"
version = "2.0.0"
interface = "image-plugin-api"
interface_version = 3

[metadata]
category = "image-processing"
min_host_version = "4.1.0"
description = "Gaussian blur filter"
tags = ["blur", "image", "filter"]

Step 3: Load and validate

Use fidius_host::package::load_package_manifest with your schema type:

use std::path::Path;
use fidius_host::package::load_package_manifest;
use fidius_core::package::PackageError;

fn load_plugin_package(dir: &Path) -> Result<(), PackageError> {
    let manifest = load_package_manifest::<PluginMetadata>(dir)?;

    println!("Loaded: {} v{}", manifest.package.name, manifest.package.version);
    println!("Category: {}", manifest.metadata.category);
    println!("Min host version: {}", manifest.metadata.min_host_version);

    if let Some(desc) = &manifest.metadata.description {
        println!("Description: {}", desc);
    }

    for tag in &manifest.metadata.tags {
        println!("Tag: {}", tag);
    }

    Ok(())
}

If the [metadata] section is missing a required field (e.g., category), load_package_manifest returns PackageError::ParseError with a message identifying the missing field.

Step 4: Discover and validate multiple packages

Use fidius_host::package::discover_packages to scan a directory for package subdirectories, then validate each one:

use fidius_host::package::{discover_packages, load_package_manifest};

fn validate_all(packages_dir: &Path) -> Result<(), PackageError> {
    let package_dirs = discover_packages(packages_dir)?;

    for dir in &package_dirs {
        match load_package_manifest::<PluginMetadata>(dir) {
            Ok(manifest) => {
                println!("Valid: {} v{}", manifest.package.name, manifest.package.version);
            }
            Err(e) => {
                eprintln!("Invalid package in {}: {}", dir.display(), e);
            }
        }
    }

    Ok(())
}

discover_packages scans the given directory for subdirectories that contain a package.toml file. It returns paths sorted alphabetically.

Untyped loading

If you need to load a manifest without schema validation (e.g., in a CLI tool), use fidius_core::package::load_manifest_untyped. This accepts any [metadata] section by deserializing it as toml::Value:

use fidius_core::package::load_manifest_untyped;

let manifest = load_manifest_untyped(Path::new("./my-package/"))?;
// manifest.metadata is toml::Value — access fields dynamically

See also