Skip to main content

wasm_link/loading/
load_plugin_tree.rs

1use std::sync::Arc ;
2use std::collections::HashMap ;
3use thiserror::Error ;
4use wasmtime::Engine;
5use wasmtime::component::Linker ;
6
7use crate::interface::{ InterfaceData, InterfaceCardinality };
8use crate::plugin::PluginData ;
9use crate::utils::PartialResult ;
10use super::{ load_socket, SocketState, LoadedSocket };
11
12
13
14/// Errors that can occur while loading and linking plugins.
15///
16/// Loading attempts to proceed gracefully, collecting errors and loading as many
17/// plugins as possible while adhering to cardinality constraints. These errors
18/// are returned via [`PartialResult`] from [`PluginTree::load`].
19///
20/// [`PartialResult`]: crate::PartialResult
21/// [`PluginTree::load`]: crate::PluginTree::load
22#[derive( Error )]
23pub enum LoadError<I: InterfaceData, P: PluginData> {
24
25    /// A plugin references a socket (dependency) that doesn't exist in the interface set.
26    #[error( "Invalid socket: {0}" )]
27    InvalidSocket( I::Id ),
28
29    /// A dependency cycle was detected. Cycles are forbidden in the plugin graph.
30    #[error( "Loop detected loading: '{0}'" )]
31    LoopDetected( I::Id ),
32
33    /// The number of plugins implementing an interface violates its cardinality.
34    /// For example, `ExactlyOne` with 0 or 2+ plugins, or `AtLeastOne` with 0 plugins.
35    #[error( "Failed to meet cardinality requirements: {0}, found {1}" )]
36    FailedCardinalityRequirements( InterfaceCardinality, usize ),
37
38    /// Failed to read interface metadata (e.g., couldn't parse WIT or access data source).
39    #[error( "Corrupted interface manifest: {0}" )]
40    CorruptedInterfaceManifest( I::Error ),
41
42    /// Failed to read plugin metadata (e.g., couldn't determine sockets or ID).
43    #[error( "Corrupted plugin manifest: {0}" )]
44    CorruptedPluginManifest( P::Error ),
45
46    /// Wasmtime failed to compile the WASM component (invalid binary or unsupported features).
47    #[error( "Failed to load component: {0}" )]
48    FailedToLoadComponent( wasmtime::Error ),
49
50    /// I/O error reading the WASM binary.
51    #[error( "Failed to read WASM data: {0}" )]
52    FailedToReadWasm( std::io::Error ),
53
54    /// Failed to link the root interface into the plugin.
55    #[error( "Failed to link root interface: {0}" )]
56    FailedToLinkInterface( wasmtime::Error ),
57
58    /// Failed to link a specific function during socket wiring.
59    #[error( "Failed to link function '{0}': {1}" )]
60    FailedToLink( String, wasmtime::Error ),
61
62    /// Internal marker for errors that have already been reported.
63    #[error( "Handled failure" )]
64    AlreadyHandled,
65
66}
67
68impl<I: InterfaceData, P: PluginData> std::fmt::Debug for LoadError<I, P> {
69    fn fmt( &self, f: &mut std::fmt::Formatter<'_> ) -> std::fmt::Result {
70        match self {
71            Self::InvalidSocket( id ) => f.debug_tuple( "InvalidSocket" ).field( id ).finish(),
72            Self::LoopDetected( id ) => f.debug_tuple( "LoopDetected" ).field( id ).finish(),
73            Self::FailedCardinalityRequirements( c, n ) => f.debug_tuple( "FailedCardinalityRequirements" ).field( c ).field( n ).finish(),
74            Self::CorruptedInterfaceManifest( e ) => f.debug_tuple( "CorruptedInterfaceManifest" ).field( e ).finish(),
75            Self::CorruptedPluginManifest( e ) => f.debug_tuple( "CorruptedPluginManifest" ).field( e ).finish(),
76            Self::FailedToLoadComponent( e ) => f.debug_tuple( "FailedToLoadComponent" ).field( e ).finish(),
77            Self::FailedToReadWasm( e ) => f.debug_tuple( "FailedToReadWasm" ).field( e ).finish(),
78            Self::FailedToLinkInterface( e ) => f.debug_tuple( "FailedToLinkInterface" ).field( e ).finish(),
79            Self::FailedToLink( name, e ) => f.debug_tuple( "FailedToLink" ).field( name ).field( e ).finish(),
80            Self::AlreadyHandled => f.debug_struct( "AlreadyHandled" ).finish(),
81        }
82    }
83}
84
85/// Result of a load operation that may have partial failures.
86/// The `errors` field contains handled load failures
87/// Convenience abstraction semantically equivalent to:
88/// `( SocketMap, LoadResult<T, LoadError, LoadError> )`
89pub(super) struct LoadResult<T, I: InterfaceData, P: PluginData + 'static> {
90    pub socket_map: HashMap<I::Id, SocketState<I, P>>,
91    pub result: Result<T, LoadError<I, P>>,
92    pub errors: Vec<LoadError<I, P>>,
93}
94
95#[allow( clippy::type_complexity )]
96#[inline] pub(crate) fn load_plugin_tree<I, P, InterfaceId>(
97    socket_map: HashMap<I::Id, ( I, Vec<P> )>,
98    engine: &Engine,
99    default_linker: &Linker<P>,
100    root: I::Id,
101) -> PartialResult<( Arc<I>, Arc<LoadedSocket<P, P::Id>> ), LoadError<I, P>, LoadError<I, P>>
102where
103    I: InterfaceData<Id = InterfaceId>,
104    P: PluginData<InterfaceId = InterfaceId>,
105    InterfaceId: Clone + std::hash::Hash + Eq,
106{
107    let socket_map = socket_map.into_iter()
108        .map(|( socket_id, ( interface, plugins ))| ( socket_id, SocketState::Unprocessed( interface, plugins )))
109        .collect();
110
111    match load_socket( socket_map, engine, default_linker, root ) {
112        LoadResult { socket_map: _, result: Ok(( interface, socket )), errors } => Ok((( interface, socket ), errors )),
113        LoadResult { socket_map: _, result: Err( err ), errors } => Err(( err, errors ))
114    }
115
116}