agent-plugins 0.1.1

Sans-IO implementation of the Agent Plugins specification (agent-plugins.org): load, validate, and prepare agent plugins from any IO
Documentation
//! A convenience layer over the sans-IO machine for synchronous sources.
//!
//! Implement [`Source`] for anything that can answer the loader's four
//! questions — a directory, an archive, a key-value map — and [`load`] runs
//! the whole conversation for you. Async IO needs no trait at all: drive
//! [`Loading`](crate::Loading) directly and `.await` between answers.

use crate::load::{Answer, DirEntry, FileKind, LoadedPlugin, Loading, Question, Rejection};
use crate::path::PackagePath;

/// A synchronous supplier of plugin package bytes.
pub trait Source {
    /// The source's own failure type. Failures are folded into the loading
    /// conversation as [`Answer::Unavailable`], where the spec's failure
    /// boundaries decide how far the damage spreads.
    type Error: std::fmt::Display;

    /// What kind of entry does `path` resolve to? `None` if absent.
    fn kind(&mut self, path: &PackagePath) -> Result<Option<FileKind>, Self::Error>;

    /// Does `path`, fully resolved, stay within the plugin root? Sources
    /// that cannot contain links may simply return `Ok(true)`.
    fn confined(&mut self, path: &PackagePath) -> Result<bool, Self::Error>;

    /// Read the file at `path`.
    fn read(&mut self, path: &PackagePath) -> Result<Vec<u8>, Self::Error>;

    /// List the immediate entries of the directory at `path`.
    fn list(&mut self, path: &PackagePath) -> Result<Vec<DirEntry>, Self::Error>;
}

/// Load a plugin by running the full loading conversation against a source.
pub fn load<S: Source>(source: &mut S) -> Result<LoadedPlugin, Rejection> {
    let mut loading = Loading::start();
    while let Some(question) = loading.question() {
        let answer = match &question {
            Question::Kind(path) => match source.kind(path) {
                Ok(kind) => Answer::Kind(kind),
                Err(e) => Answer::Unavailable { detail: e.to_string() },
            },
            Question::Confined(path) => match source.confined(path) {
                Ok(confined) => Answer::Confined(confined),
                Err(e) => Answer::Unavailable { detail: e.to_string() },
            },
            Question::Read(path) => match source.read(path) {
                Ok(bytes) => Answer::Bytes(bytes),
                Err(e) => Answer::Unavailable { detail: e.to_string() },
            },
            Question::List(path) => match source.list(path) {
                Ok(entries) => Answer::Entries(entries),
                Err(e) => Answer::Unavailable { detail: e.to_string() },
            },
        };
        loading.answer(answer).expect("answer variant matches the question just asked");
    }
    loading.finish()
}