vtx-sdk 0.1.14

Official SDK for developing VTX plugins using Rust and WebAssembly.
Documentation
//! Low-boilerplate plugin exports.

use crate::error::{VtxError, VtxResult};
use crate::modules::event::PluginEvent;
use crate::modules::net::http::{Request, Response, ResponseBuilder};
use crate::{Capabilities, Manifest, UserContext};

/// A high-level trait for defining Vtx plugins with minimal boilerplate.
///
/// The `bindings::Guest` trait generated by `wit-bindgen` requires implementing all exported functions.
/// This trait provides default implementations for common methods and allows returning `VtxResult`
/// directly in `handle` and `handle_event` for better error handling ergonomics.
pub trait VtxPlugin {
    fn handle(_req: Request) -> VtxResult<Response> {
        Ok(ResponseBuilder::not_found())
    }

    fn handle_event(_event: PluginEvent) -> VtxResult<()> {
        Ok(())
    }

    fn get_migrations() -> Vec<String> {
        Vec::new()
    }

    fn get_manifest() -> Manifest;

    fn get_resources() -> Vec<String> {
        Vec::new()
    }

    fn get_capabilities() -> Capabilities;

    /// Performs authentication based on request headers.
    ///
    /// By default, this returns 401 (Unauthorized), indicating that this plugin does not
    /// handle authentication. This design ensures it does not block other plugins in the
    /// chain of responsibility that might handle authentication.
    fn authenticate(_headers: &[(String, String)]) -> VtxResult<UserContext> {
        Err(VtxError::AuthDenied(401))
    }
}

/// Exports a type implementing `VtxPlugin` as the `world vtx-plugin` Guest for WIT.
///
/// Usage:
/// - `export_plugin!(MyPlugin)`: Generates a default guest adapter and exports it.
/// - `export_plugin!(MyPlugin => MyGuest)`: Defines a custom adapter name (useful to avoid naming
///   conflicts when the macro is called multiple times within the same crate).
#[macro_export]
macro_rules! export_plugin {
    ($plugin:ty) => {
        $crate::export_plugin!($plugin => __VtxSdkGuest);
    };
    ($plugin:ty => $guest:ident) => {
        struct $guest;

        impl $crate::bindings::Guest for $guest {
            fn handle(req: $crate::http::Request) -> $crate::http::Response {
                match <$plugin as $crate::plugin::VtxPlugin>::handle(req) {
                    Ok(resp) => resp,
                    Err(err) => $crate::http::ResponseBuilder::error(err),
                }
            }

            fn handle_event(event: $crate::event::PluginEvent) -> Result<(), String> {
                match <$plugin as $crate::plugin::VtxPlugin>::handle_event(event) {
                    Ok(()) => Ok(()),
                    Err(err) => Err(err.to_string()),
                }
            }

            fn get_migrations() -> Vec<String> {
                <$plugin as $crate::plugin::VtxPlugin>::get_migrations()
            }

            fn get_manifest() -> $crate::Manifest {
                <$plugin as $crate::plugin::VtxPlugin>::get_manifest()
            }

            fn get_resources() -> Vec<String> {
                <$plugin as $crate::plugin::VtxPlugin>::get_resources()
            }

            fn get_capabilities() -> $crate::Capabilities {
                <$plugin as $crate::plugin::VtxPlugin>::get_capabilities()
            }

            fn authenticate(
                headers: Vec<(String, String)>,
            ) -> Result<$crate::UserContext, u16> {
                use $crate::auth::IntoAuthResult as _;
                <$plugin as $crate::plugin::VtxPlugin>::authenticate(&headers).into_auth_result()
            }
        }

        $crate::export!($guest);
    };
}