numcodecs_wasm_host/
component.rs1use std::sync::Arc;
2
3use schemars::Schema;
4use serde::Deserializer;
5use wasm_component_layer::{
6 AsContextMut, ComponentList, ExportInstance, Func, Instance, TypedFunc, Value,
7};
8
9use crate::{
10 codec::WasmCodec,
11 error::RuntimeError,
12 wit::{guest_error_from_wasm, NumcodecsWitInterfaces},
13};
14
15pub struct WasmCodecComponent {
22 pub(crate) codec_id: Arc<str>,
24 pub(crate) codec_config_schema: Arc<Schema>,
25 pub(crate) from_config: Func,
28 pub(crate) encode: Func,
29 pub(crate) decode: Func,
30 pub(crate) decode_into: Func,
31 pub(crate) get_config: Func,
32 pub(crate) instance: Instance,
34}
35
36impl WasmCodecComponent {
37 pub fn new(mut ctx: impl AsContextMut, instance: Instance) -> Result<Self, RuntimeError> {
55 fn load_func(interface: &ExportInstance, name: &str) -> Result<Func, RuntimeError> {
56 let Some(func) = interface.func(name) else {
57 return Err(RuntimeError::from(anyhow::Error::msg(format!(
58 "WASM component interface does not contain a function named `{name}`"
59 ))));
60 };
61
62 Ok(func)
63 }
64
65 fn load_typed_func<P: ComponentList, R: ComponentList>(
66 interface: &ExportInstance,
67 name: &str,
68 ) -> Result<TypedFunc<P, R>, RuntimeError> {
69 load_func(interface, name)?
70 .typed()
71 .map_err(RuntimeError::from)
72 }
73
74 let interfaces = NumcodecsWitInterfaces::get();
75
76 let Some(codecs_interface) = instance.exports().instance(&interfaces.codec) else {
77 return Err(RuntimeError::from(anyhow::Error::msg(format!(
78 "WASM component does not contain an interface named `{}`",
79 interfaces.codec
80 ))));
81 };
82
83 let codec_id = load_typed_func(codecs_interface, "codec-id")?;
84 let codec_id = codec_id.call(&mut ctx, ())?;
85
86 let codec_config_schema = load_typed_func(codecs_interface, "codec-config-schema")?;
87 let codec_config_schema: Arc<str> = codec_config_schema.call(&mut ctx, ())?;
88 let codec_config_schema: Schema =
89 serde_json::from_str(&codec_config_schema).map_err(anyhow::Error::new)?;
90
91 Ok(Self {
92 codec_id,
93 codec_config_schema: Arc::new(codec_config_schema),
94 from_config: load_func(codecs_interface, "[static]codec.from-config")?,
95 encode: load_func(codecs_interface, "[method]codec.encode")?,
96 decode: load_func(codecs_interface, "[method]codec.decode")?,
97 decode_into: load_func(codecs_interface, "[method]codec.decode-into")?,
98 get_config: load_func(codecs_interface, "[method]codec.get-config")?,
99 instance,
100 })
101 }
102}
103
104impl WasmCodecComponent {
106 #[must_use]
108 pub fn codec_id(&self) -> &str {
109 &self.codec_id
110 }
111
112 #[must_use]
114 pub fn codec_config_schema(&self) -> &Schema {
115 &self.codec_config_schema
116 }
117
118 pub fn codec_from_config<'de, D: Deserializer<'de>>(
132 &self,
133 mut ctx: impl AsContextMut,
134 config: D,
135 ) -> Result<WasmCodec, D::Error> {
136 let mut config_bytes = Vec::new();
137 serde_transcode::transcode(config, &mut serde_json::Serializer::new(&mut config_bytes))
138 .map_err(serde::de::Error::custom)?;
139 let config = String::from_utf8(config_bytes).map_err(serde::de::Error::custom)?;
140
141 let args = Value::String(config.into());
142 let mut result = Value::U8(0);
143
144 self.from_config
145 .call(
146 &mut ctx,
147 std::slice::from_ref(&args),
148 std::slice::from_mut(&mut result),
149 )
150 .map_err(serde::de::Error::custom)?;
151
152 let codec = match result {
153 Value::Result(result) => match &*result {
154 Ok(Some(Value::Own(resource))) => WasmCodec {
155 resource: resource.clone(),
156 codec_id: self.codec_id.clone(),
157 codec_config_schema: self.codec_config_schema.clone(),
158 from_config: self.from_config.clone(),
159 encode: self.encode.clone(),
160 decode: self.decode.clone(),
161 decode_into: self.decode_into.clone(),
162 get_config: self.get_config.clone(),
163 instance: self.instance.clone(),
164 },
165 Err(err) => match guest_error_from_wasm(err.as_ref()) {
166 Ok(err) => return Err(serde::de::Error::custom(err)),
167 Err(err) => return Err(serde::de::Error::custom(err)),
168 },
169 result => {
170 return Err(serde::de::Error::custom(format!(
171 "unexpected from-config result value {result:?}"
172 )))
173 }
174 },
175 value => {
176 return Err(serde::de::Error::custom(format!(
177 "unexpected from-config result value {value:?}"
178 )))
179 }
180 };
181
182 Ok(codec)
183 }
184}