Skip to main content

composable_runtime/
types.rs

1//! Core type definitions shared across the crate.
2
3use anyhow::Result;
4use std::any::{Any, TypeId};
5use std::collections::HashMap;
6use std::fmt;
7use std::future::Future;
8use std::pin::Pin;
9
10pub fn default_scope() -> String {
11    "any".to_string()
12}
13
14/// Capability definition (built-in and custom capabilities).
15#[derive(Debug, Clone)]
16pub struct CapabilityDefinition {
17    pub name: String,
18    pub kind: String,
19    pub scope: String,
20    pub properties: HashMap<String, serde_json::Value>,
21}
22
23/// Component definition.
24#[derive(Debug, Clone)]
25pub struct ComponentDefinition {
26    pub name: String,
27    pub uri: String,
28    pub scope: String,
29    pub imports: Vec<String>,
30    pub interceptors: Vec<String>,
31    pub config: HashMap<String, serde_json::Value>,
32}
33
34/// State passed to Wasm components during execution.
35pub struct ComponentState {
36    pub wasi_ctx: wasmtime_wasi::WasiCtx,
37    pub wasi_http_ctx: Option<wasmtime_wasi_http::WasiHttpCtx>,
38    pub resource_table: wasmtime_wasi::ResourceTable,
39    pub(crate) extensions: HashMap<TypeId, Box<dyn Any + Send>>,
40}
41
42impl ComponentState {
43    /// Get a reference to an extension by type.
44    pub fn get_extension<T: 'static + Send>(&self) -> Option<&T> {
45        self.extensions
46            .get(&TypeId::of::<T>())
47            .and_then(|boxed| boxed.downcast_ref())
48    }
49
50    /// Get a mutable reference to an extension by type.
51    pub fn get_extension_mut<T: 'static + Send>(&mut self) -> Option<&mut T> {
52        self.extensions
53            .get_mut(&TypeId::of::<T>())
54            .and_then(|boxed| boxed.downcast_mut())
55    }
56
57    /// Set an extension value by type.
58    pub fn set_extension<T: 'static + Send>(&mut self, value: T) {
59        self.extensions.insert(TypeId::of::<T>(), Box::new(value));
60    }
61}
62
63/// A validated WebAssembly Interface Type (WIT) interface name.
64/// Format: `namespace:package/interface[@version]`
65#[derive(Debug, Clone, PartialEq, Eq, Hash)]
66pub struct Interface {
67    namespace: String,
68    package: String,
69    interface: String,
70    version: Option<String>,
71    full_name: String,
72}
73
74impl Interface {
75    /// Parse and validate a WIT interface string.
76    pub fn parse(s: &str) -> Result<Self> {
77        if let Some((namespace, rest)) = s.split_once(':')
78            && let Some((package, after_slash)) = rest.split_once('/')
79        {
80            let (interface, version) = if let Some((i, v)) = after_slash.split_once('@') {
81                (i, Some(v.to_string()))
82            } else {
83                (after_slash, None)
84            };
85
86            return Ok(Self {
87                namespace: namespace.to_string(),
88                package: package.to_string(),
89                interface: interface.to_string(),
90                version,
91                full_name: s.to_string(),
92            });
93        }
94
95        Err(anyhow::anyhow!(
96            "Invalid WIT interface format: expected namespace:package/interface[@version], got: {s}"
97        ))
98    }
99
100    /// Get the full interface string.
101    pub fn as_str(&self) -> &str {
102        &self.full_name
103    }
104
105    /// Get the namespace (e.g., "wasi" from "wasi:http/outgoing-handler@0.2.6").
106    pub fn namespace(&self) -> &str {
107        &self.namespace
108    }
109
110    /// Get the package (e.g., "http" from "wasi:http/outgoing-handler@0.2.6").
111    pub fn package(&self) -> &str {
112        &self.package
113    }
114
115    /// Get the interface name (e.g., "outgoing-handler" from "wasi:http/outgoing-handler@0.2.6").
116    pub fn interface_name(&self) -> &str {
117        &self.interface
118    }
119
120    /// Get the version (e.g., Some("0.2.6") from "wasi:http/outgoing-handler@0.2.6").
121    pub fn version(&self) -> Option<&str> {
122        self.version.as_deref()
123    }
124}
125
126impl fmt::Display for Interface {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        write!(f, "{}", self.full_name)
129    }
130}
131
132/// A function specification.
133#[derive(Debug, Clone, PartialEq, Eq, Hash)]
134pub struct Function {
135    interface: Option<Interface>,
136    function_name: String,
137    docs: String,
138    params: Vec<FunctionParam>,
139    result: Option<serde_json::Value>,
140}
141
142impl Function {
143    /// Create a new function specification.
144    pub fn new(
145        interface: Option<Interface>,
146        function_name: String,
147        docs: String,
148        params: Vec<FunctionParam>,
149        result: Option<serde_json::Value>,
150    ) -> Self {
151        Self {
152            interface,
153            function_name,
154            docs,
155            params,
156            result,
157        }
158    }
159
160    /// Get the interface (None for direct function exports)
161    pub fn interface(&self) -> Option<&Interface> {
162        self.interface.as_ref()
163    }
164
165    /// Get the function name.
166    pub fn function_name(&self) -> &str {
167        &self.function_name
168    }
169
170    /// Get the function documentation.
171    pub fn docs(&self) -> &str {
172        &self.docs
173    }
174
175    /// Get the function parameters.
176    pub fn params(&self) -> &[FunctionParam] {
177        &self.params
178    }
179
180    /// Get the function result type.
181    pub fn result(&self) -> Option<&serde_json::Value> {
182        self.result.as_ref()
183    }
184
185    /// Get the function key used in maps and invoke calls.
186    /// - Direct function exports: `function_name`
187    /// - Interface function exports: `unqualified_interface.function_name`
188    pub fn key(&self) -> String {
189        match &self.interface {
190            Some(iface) => format!("{}.{}", iface.interface_name(), self.function_name),
191            None => self.function_name.clone(),
192        }
193    }
194}
195
196impl fmt::Display for Function {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        match &self.interface {
199            Some(iface) => write!(f, "{}#{}", iface, self.function_name),
200            None => write!(f, "{}", self.function_name),
201        }
202    }
203}
204
205/// A function parameter specification.
206#[derive(Clone, Debug, PartialEq, Eq, Hash)]
207pub struct FunctionParam {
208    pub name: String,
209    pub is_optional: bool,
210    pub json_schema: serde_json::Value,
211}
212
213/// A named Wasm Component and its exported functions.
214#[derive(Debug, Clone)]
215pub struct Component {
216    pub name: String,
217    pub functions: HashMap<String, Function>,
218}
219
220/// Invoke components by name.
221pub trait ComponentInvoker: Send + Sync {
222    fn get_component(&self, name: &str) -> Option<Component>;
223
224    fn invoke<'a>(
225        &'a self,
226        component_name: &'a str,
227        function_name: &'a str,
228        args: Vec<serde_json::Value>,
229    ) -> Pin<Box<dyn Future<Output = Result<serde_json::Value>> + Send + 'a>>;
230}
231
232/// Publish messages to channels by name.
233pub trait MessagePublisher: Send + Sync {
234    fn publish<'a>(
235        &'a self,
236        channel: &'a str,
237        body: Vec<u8>,
238        headers: HashMap<String, String>,
239    ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
240}