Skip to content

Describe each format in Rust and remove plugin.toml #444

Closed

Description

@schmitch

Part of #442.

Where things stand (after #437)

Each format crate has a plugin.toml next to its Cargo.toml:

[plugin]
id = "nuget"
name = "NuGet"
version = "1.0.0"
api = "0.3.0"
description = "NuGet V3 feeds: …"
mount = "/v3"
kinds = ["hosted", "proxy", "group"]

[assets]
package = "package.nupkg"
package_type = "application/octet-stream"
manifest = "package.nuspec"
manifest_type = "application/xml"

The crate hands this file to the server as text: pub const PLUGIN_TOML: &str = forge_plugin_api::plugin_toml!();, which is an include_str!. The server parses it at startup:

  • Plugin::builtin(PLUGIN_TOML, Nuget::with_limits(…)).expect("plugins/nuget/plugin.toml is valid");
  • Manifest::parse with the toml crate;
  • Box::leak on the id to get a &'static str.

The file was the manifest an operator installed next to a WebAssembly component. Now that only built-in formats remain, it holds values the compiler could check, written in a second language:

  • a typo in a key or a kind fails at startup instead of at build time;
  • version repeats the crate's version, and a unit test in each crate keeps the two equal;
  • api can only be forge_plugin_api::API, because the contract and the formats build together and the compiler already checks the trait;
  • every format crate repeats the same plugin_toml_names_this_crate test.

Proposal

Each format describes itself in Rust, as a 'static value that the trait returns:

pub const DESCRIPTOR: Descriptor = Descriptor {
    id: "nuget",
    name: "NuGet",
    version: env!("CARGO_PKG_VERSION"),
    description: "NuGet V3 feeds: …",
    kinds: &[RepositoryKind::Hosted, RepositoryKind::Proxy, RepositoryKind::Group],
    assets: Assets {
        package: "package.nupkg",
        package_type: "application/octet-stream",
        manifest: "package.nuspec",
        manifest_type: "application/xml",
    },
};

impl FormatPlugin for Nuget {
    fn descriptor(&self) -> &'static Descriptor {
        &DESCRIPTOR
    }
    // …
}
  • Descriptor (the name is open) goes in forge-plugin-api, next to RepositoryKind and Assets. The fields of Assets become &'static str, and an Assets::DEFAULT replaces the serde default. The descriptor comes from a trait method rather than an associated const because the server holds formats as Arc<dyn FormatPlugin>.
  • Plugin::builtin(implementation) takes only the format. Plugin::manifest() becomes descriptor(), and Plugin::id() returns descriptor().id with no leak.
  • The startup parser validated the id's spelling, the asset names and the reserved ids (RESERVED_IDS: docker and the server's routes). Those checks move into a test over plugins::builtin() in forge-server, together with a check that no two formats share an id. A bad descriptor then fails cargo test instead of startup.
  • storage from Raw repositories as a format plugin: a file store in the plugin contract #431 becomes a descriptor field like the others, and so does mount until Serve every repository at /repository/{name}/, with no format in the URL (breaking) #438 removes it. If Serve every repository at /repository/{name}/, with no format in the URL (breaking) #438 lands after this, it removes the mount field instead of the manifest key, and its criterion that a plugin.toml with mount is refused no longer applies.
  • Remove:
    • plugin.toml from every format crate;
    • plugin_toml! and PLUGIN_TOML;
    • Manifest::parse and Manifest::load, PluginError::Manifest and SUPPORTED_APIS;
    • forge_plugin_api::API and the api key;
    • the plugin_toml_names_this_crate tests;
    • the "keep it equal to version in plugin.toml" comments in the crates' Cargo.toml;
    • forge-plugin's toml dependency.
  • Update the docs: the crates' README and Writing a plugin in docs/format-plugins.md show the descriptor instead of plugin.toml.

Alternatives considered

  • Keep plugin.toml. Only the crate it sits in reads it. Keeping it keeps the startup parse and the duplicated version.
  • Use [package.metadata.forge] in Cargo.toml. This saves one file, but the values are still TOML that something parses at runtime or in a build script, with no type checking.

No changelog fragment is needed, since nothing changes for anyone running Forge. GET /admin/formats answers as before.

Acceptance criteria

  • There is no plugin.toml in the repository, and git grep -n 'plugin\.toml\|plugin_toml\|PLUGIN_TOML' finds nothing outside CHANGELOG.md and docs/changes/.
  • cargo tree -p forge-plugin shows no toml.
  • GET /admin/formats returns the same ids, names, versions, descriptions and kinds as before, and the existing admin tests pass unchanged.
  • A test over the built-in formats refuses a malformed or reserved id, two formats with the same id, and invalid asset names.
  • The docs show a format's descriptor, not plugin.toml.

Blocked by #437: a WebAssembly component needs its manifest on disk, so the file has to stay for as long as components can load. This can land before or after #443, but not at the same time, because both change the same files.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions