1use std::{error::Error, sync::Arc};
2
3use numcodecs::{Codec, DynCodec, DynCodecType, ErasedDynCodec, ErasedDynCodecType};
4use numcodecs_registry::Registry;
5use wasm_component_layer::{
6 AsContext, AsContextMut, Func, FuncType, Linker, List, ListType, Record, RecordType,
7 ResourceOwn, ResourceType, ResultType, ResultValue, TypeIdentifier, Value, ValueType,
8};
9
10use crate::{WasmCodec, wit::NumcodecsWitInterfaces};
11
12#[expect(clippy::too_many_lines)] pub fn add_registry_to_linker(
20 linker: &mut Linker,
21 mut ctx: impl AsContextMut,
22 registry: impl Registry,
23) -> Result<(), anyhow::Error> {
24 let NumcodecsWitInterfaces {
25 registry: numcodecs_registry_interface,
26 ..
27 } = NumcodecsWitInterfaces::get();
28
29 let registry = Arc::new(registry);
30
31 let numcodecs_types_error_record = RecordType::new(
32 None, [
34 ("message", ValueType::String),
35 ("chain", ValueType::List(ListType::new(ValueType::String))),
36 ],
37 )?;
38
39 let numcodecs_registry_instance =
40 linker.define_instance(numcodecs_registry_interface.clone())?;
41
42 let numcodecs_registry_external_codec_resource = ResourceType::with_destructor(
43 ctx.as_context_mut(),
44 Some(TypeIdentifier::new(
45 "external-codec",
46 Some(numcodecs_registry_interface.clone()),
47 )),
48 |_ctx, codec: ErasedDynCodec| {
49 std::mem::drop(codec);
50 Ok(())
51 },
52 )?;
53
54 numcodecs_registry_instance.define_resource(
55 "external-codec",
56 numcodecs_registry_external_codec_resource.clone(),
57 )?;
58
59 let numcodecs_registry_external_codec_type_resource = ResourceType::with_destructor(
60 ctx.as_context_mut(),
61 Some(TypeIdentifier::new(
62 "external-codec-type",
63 Some(numcodecs_registry_interface.clone()),
64 )),
65 |_ctx, codec_ty: ErasedDynCodecType| {
66 std::mem::drop(codec_ty);
67 Ok(())
68 },
69 )?;
70
71 numcodecs_registry_instance.define_resource(
72 "external-codec-type",
73 numcodecs_registry_external_codec_type_resource.clone(),
74 )?;
75
76 let any_array_record = WasmCodec::any_array_ty().clone();
77
78 let any_array_result = ResultType::new(
79 Some(ValueType::Record(any_array_record.clone())),
80 Some(ValueType::Record(numcodecs_types_error_record.clone())),
81 );
82
83 let my_any_array_result = any_array_result.clone();
84 let my_numcodecs_types_error_record = numcodecs_types_error_record.clone();
85 let external_codec_encode = Func::new(
86 ctx.as_context_mut(),
87 FuncType::new(
88 [
89 ValueType::Borrow(numcodecs_registry_external_codec_resource.clone()),
90 ValueType::Record(any_array_record.clone()),
91 ],
92 [ValueType::Result(any_array_result.clone())],
93 ),
94 move |ctx, args, results| {
95 let [Value::Borrow(codec), Value::Record(data)] = args else {
96 anyhow::bail!(
97 "invalid numcodecs:abc/registry#[method]external-codec.encode arguments"
98 );
99 };
100
101 let [result] = results else {
102 anyhow::bail!(
103 "invalid numcodecs:abc/registry#[method]external-codec.encode results"
104 );
105 };
106
107 let encoded = WasmCodec::with_array_view_from_wasm_record(data, |data| {
108 let ctx = ctx.as_context();
109 let codec: &ErasedDynCodec = codec.rep(&ctx)?;
110
111 let encoded = codec.encode(data.cow()).map_err(anyhow::Error::new)?;
112 Ok(encoded)
113 });
114
115 let encoded = match encoded {
116 Ok(encoded) => Ok(WasmCodec::array_into_wasm(encoded.view())?),
117 Err(err) => Err(into_wit_error(err, &my_numcodecs_types_error_record)?),
118 };
119
120 let res = match encoded {
121 Ok(encoded) => Ok(Some(Value::Record(encoded))),
122 Err(err) => Err(Some(Value::Record(err))),
123 };
124
125 *result = Value::Result(ResultValue::new(my_any_array_result.clone(), res)?);
126
127 Ok(())
128 },
129 );
130 numcodecs_registry_instance
131 .define_func("[method]external-codec.encode", external_codec_encode)?;
132
133 let my_any_array_result = any_array_result.clone();
134 let my_numcodecs_types_error_record = numcodecs_types_error_record.clone();
135 let external_codec_decode = Func::new(
136 ctx.as_context_mut(),
137 FuncType::new(
138 [
139 ValueType::Borrow(numcodecs_registry_external_codec_resource.clone()),
140 ValueType::Record(any_array_record.clone()),
141 ],
142 [ValueType::Result(any_array_result.clone())],
143 ),
144 move |ctx, args, results| {
145 let [Value::Borrow(codec), Value::Record(encoded)] = args else {
146 anyhow::bail!(
147 "invalid numcodecs:abc/registry#[method]external-codec.decode arguments"
148 );
149 };
150
151 let [result] = results else {
152 anyhow::bail!(
153 "invalid numcodecs:abc/registry#[method]external-codec.decode results"
154 );
155 };
156
157 let decoded = WasmCodec::with_array_view_from_wasm_record(encoded, |encoded| {
158 let ctx = ctx.as_context();
159 let codec: &ErasedDynCodec = codec.rep(&ctx)?;
160
161 let decoded = codec.decode(encoded.cow()).map_err(anyhow::Error::new)?;
162 Ok(decoded)
163 });
164
165 let decoded = match decoded {
166 Ok(decoded) => Ok(WasmCodec::array_into_wasm(decoded.view())?),
167 Err(err) => Err(into_wit_error(err, &my_numcodecs_types_error_record)?),
168 };
169
170 let res = match decoded {
171 Ok(decoded) => Ok(Some(Value::Record(decoded))),
172 Err(err) => Err(Some(Value::Record(err))),
173 };
174
175 *result = Value::Result(ResultValue::new(my_any_array_result.clone(), res)?);
176
177 Ok(())
178 },
179 );
180 numcodecs_registry_instance
181 .define_func("[method]external-codec.decode", external_codec_decode)?;
182
183 let any_array_prototype_record = WasmCodec::any_array_prototype_ty().clone();
184
185 let my_any_array_result = any_array_result.clone();
186 let my_numcodecs_types_error_record = numcodecs_types_error_record.clone();
187 let external_codec_decode_into = Func::new(
188 ctx.as_context_mut(),
189 FuncType::new(
190 [
191 ValueType::Borrow(numcodecs_registry_external_codec_resource.clone()),
192 ValueType::Record(any_array_record),
193 ValueType::Record(any_array_prototype_record),
194 ],
195 [ValueType::Result(any_array_result)],
196 ),
197 move |ctx, args, results| {
198 let [
199 Value::Borrow(codec),
200 Value::Record(encoded),
201 Value::Record(decoded),
202 ] = args
203 else {
204 anyhow::bail!(
205 "invalid numcodecs:abc/registry#[method]external-codec.decode-into arguments"
206 );
207 };
208
209 let [result] = results else {
210 anyhow::bail!(
211 "invalid numcodecs:abc/registry#[method]external-codec.decode-into results"
212 );
213 };
214
215 let mut decoded = WasmCodec::array_prototype_from_wasm_record(decoded)?;
216
217 let res = WasmCodec::with_array_view_from_wasm_record(encoded, |encoded| {
218 let ctx = ctx.as_context();
219 let codec: &ErasedDynCodec = codec.rep(&ctx)?;
220
221 codec
222 .decode_into(encoded, decoded.view_mut())
223 .map_err(anyhow::Error::new)?;
224 Ok(())
225 });
226
227 let decoded = match res {
228 Ok(()) => Ok(WasmCodec::array_into_wasm(decoded.view())?),
229 Err(err) => Err(into_wit_error(err, &my_numcodecs_types_error_record)?),
230 };
231
232 let res = match decoded {
233 Ok(decoded) => Ok(Some(Value::Record(decoded))),
234 Err(err) => Err(Some(Value::Record(err))),
235 };
236
237 *result = Value::Result(ResultValue::new(my_any_array_result.clone(), res)?);
238
239 Ok(())
240 },
241 );
242 numcodecs_registry_instance.define_func(
243 "[method]external-codec.decode-into",
244 external_codec_decode_into,
245 )?;
246
247 let my_numcodecs_registry_codec_resource = numcodecs_registry_external_codec_resource.clone();
248 let external_codec_clone = Func::new(
249 ctx.as_context_mut(),
250 FuncType::new(
251 [ValueType::Borrow(
252 numcodecs_registry_external_codec_resource.clone(),
253 )],
254 [ValueType::Own(
255 numcodecs_registry_external_codec_resource.clone(),
256 )],
257 ),
258 move |ctx, args, results| {
259 let [Value::Borrow(codec)] = args else {
260 anyhow::bail!(
261 "invalid numcodecs:abc/registry#[method]external-codec.clone arguments"
262 );
263 };
264
265 let [result] = results else {
266 anyhow::bail!(
267 "invalid numcodecs:abc/registry#[method]external-codec.clone results"
268 );
269 };
270
271 let codec = {
272 let ctx = ctx.as_context();
273 let codec: &ErasedDynCodec = codec.rep(&ctx)?;
274 codec.clone()
275 };
276
277 *result = Value::Own(ResourceOwn::new(
278 ctx,
279 codec,
280 my_numcodecs_registry_codec_resource.clone(),
281 )?);
282
283 Ok(())
284 },
285 );
286 numcodecs_registry_instance
287 .define_func("[method]external-codec.clone", external_codec_clone)?;
288
289 let string_result = ResultType::new(
290 Some(ValueType::String),
291 Some(ValueType::Record(numcodecs_types_error_record.clone())),
292 );
293
294 let my_numcodecs_types_error_record = numcodecs_types_error_record.clone();
295 let external_codec_get_config = Func::new(
296 ctx.as_context_mut(),
297 FuncType::new(
298 [ValueType::Borrow(
299 numcodecs_registry_external_codec_resource.clone(),
300 )],
301 [ValueType::Result(string_result.clone())],
302 ),
303 move |ctx, args, results| {
304 let [Value::Borrow(codec)] = args else {
305 anyhow::bail!(
306 "invalid numcodecs:abc/registry#[method]external-codec.get-config arguments"
307 );
308 };
309
310 let [result] = results else {
311 anyhow::bail!(
312 "invalid numcodecs:abc/registry#[method]external-codec.get-config results"
313 );
314 };
315
316 let config = {
317 let ctx = ctx.as_context();
318 let codec: &ErasedDynCodec = codec.rep(&ctx)?;
319
320 let mut config_bytes = Vec::new();
321 match codec.get_config(&mut serde_json::Serializer::new(&mut config_bytes)) {
322 Ok(()) => match String::from_utf8(config_bytes) {
323 Ok(config) => Ok(config),
324 Err(err) => Err(into_wit_error(err, &my_numcodecs_types_error_record)?),
325 },
326 Err(err) => Err(into_wit_error(err, &my_numcodecs_types_error_record)?),
327 }
328 };
329
330 let res = match config {
331 Ok(config) => Ok(Some(Value::String(Arc::from(config)))),
332 Err(err) => Err(Some(Value::Record(err))),
333 };
334
335 *result = Value::Result(ResultValue::new(string_result.clone(), res)?);
336
337 Ok(())
338 },
339 );
340 numcodecs_registry_instance.define_func(
341 "[method]external-codec.get-config",
342 external_codec_get_config,
343 )?;
344
345 let my_numcodecs_registry_codec_type_resource =
346 numcodecs_registry_external_codec_type_resource.clone();
347 let external_codec_ty = Func::new(
348 ctx.as_context_mut(),
349 FuncType::new(
350 [ValueType::Borrow(
351 numcodecs_registry_external_codec_resource.clone(),
352 )],
353 [ValueType::Own(
354 numcodecs_registry_external_codec_type_resource.clone(),
355 )],
356 ),
357 move |ctx, args, results| {
358 let [Value::Borrow(codec)] = args else {
359 anyhow::bail!("invalid numcodecs:abc/registry#[method]external-codec.ty arguments");
360 };
361
362 let [result] = results else {
363 anyhow::bail!("invalid numcodecs:abc/registry#[method]external-codec.ty results");
364 };
365
366 let ty = {
367 let ctx = ctx.as_context();
368 let codec: &ErasedDynCodec = codec.rep(&ctx)?;
369 codec.ty()
370 };
371
372 *result = Value::Own(ResourceOwn::new(
373 ctx,
374 ty,
375 my_numcodecs_registry_codec_type_resource.clone(),
376 )?);
377
378 Ok(())
379 },
380 );
381 numcodecs_registry_instance.define_func("[method]external-codec.ty", external_codec_ty)?;
382
383 let external_codec_type_id = Func::new(
384 ctx.as_context_mut(),
385 FuncType::new(
386 [ValueType::Borrow(
387 numcodecs_registry_external_codec_type_resource.clone(),
388 )],
389 [ValueType::String],
390 ),
391 move |ctx, args, results| {
392 let [Value::Borrow(ty)] = args else {
393 anyhow::bail!(
394 "invalid numcodecs:abc/registry#[method]external-codec-type.codec-id arguments"
395 );
396 };
397
398 let [result] = results else {
399 anyhow::bail!(
400 "invalid numcodecs:abc/registry#[method]external-codectype.codec-id results"
401 );
402 };
403
404 let ctx = ctx.as_context();
405 let ty: &ErasedDynCodecType = ty.rep(&ctx)?;
406
407 *result = Value::String(Arc::from(ty.codec_id()));
408
409 Ok(())
410 },
411 );
412 numcodecs_registry_instance.define_func(
413 "[method]external-codec-type.codec-id",
414 external_codec_type_id,
415 )?;
416
417 let external_codec_type_schema = Func::new(
418 ctx.as_context_mut(),
419 FuncType::new(
420 [ValueType::Borrow(
421 numcodecs_registry_external_codec_type_resource.clone(),
422 )],
423 [ValueType::String],
424 ),
425 move |ctx, args, results| {
426 let [Value::Borrow(ty)] = args else {
427 anyhow::bail!(
428 "invalid numcodecs:abc/registry#[method]external-codec-type.codec-config-schema arguments"
429 );
430 };
431
432 let [result] = results else {
433 anyhow::bail!(
434 "invalid numcodecs:abc/registry#[method]external-codectype.codec-config-schema results"
435 );
436 };
437
438 let ctx = ctx.as_context();
439 let ty: &ErasedDynCodecType = ty.rep(&ctx)?;
440
441 *result = Value::String(Arc::from(ty.codec_config_schema().to_value().to_string()));
442
443 Ok(())
444 },
445 );
446 numcodecs_registry_instance.define_func(
447 "[method]external-codec-type.codec-config-schema",
448 external_codec_type_schema,
449 )?;
450
451 let codec_result = ResultType::new(
452 Some(ValueType::Own(
453 numcodecs_registry_external_codec_resource.clone(),
454 )),
455 Some(ValueType::Record(numcodecs_types_error_record.clone())),
456 );
457
458 let my_numcodecs_registry_codec_resource = numcodecs_registry_external_codec_resource.clone();
459 let my_numcodecs_types_error_record = numcodecs_types_error_record.clone();
460 let my_codec_result = codec_result.clone();
461 let external_codec_from_config = Func::new(
462 ctx.as_context_mut(),
463 FuncType::new(
464 [
465 ValueType::Borrow(numcodecs_registry_external_codec_type_resource),
466 ValueType::String,
467 ],
468 [ValueType::Result(my_codec_result.clone())],
469 ),
470 move |ctx, args, results| {
471 let [Value::Borrow(ty), Value::String(config)] = args else {
472 anyhow::bail!(
473 "invalid numcodecs:abc/registry#[method]external-codec-type.codec-from-config arguments"
474 );
475 };
476
477 let [result] = results else {
478 anyhow::bail!(
479 "invalid numcodecs:abc/registry#[method]external-codectype.codec-from-config results"
480 );
481 };
482
483 let res = {
484 let ctx = ctx.as_context();
485 let ty: &ErasedDynCodecType = ty.rep(&ctx)?;
486 ty.codec_from_config(&mut serde_json::Deserializer::from_str(config))
487 };
488
489 let res = match res {
490 Ok(codec) => Ok(Some(Value::Own(ResourceOwn::new(
491 ctx,
492 codec,
493 my_numcodecs_registry_codec_resource.clone(),
494 )?))),
495 Err(err) => Err(Some(Value::Record(into_wit_error(
496 err,
497 &my_numcodecs_types_error_record,
498 )?))),
499 };
500
501 *result = Value::Result(ResultValue::new(my_codec_result.clone(), res)?);
502
503 Ok(())
504 },
505 );
506 numcodecs_registry_instance.define_func(
507 "[method]external-codec-type.codec-from-config",
508 external_codec_from_config,
509 )?;
510
511 let my_numcodecs_registry_codec_resource = numcodecs_registry_external_codec_resource;
512 let my_numcodecs_types_error_record = numcodecs_types_error_record;
513 let my_codec_result = codec_result;
514 let get_external_codec = Func::new(
515 ctx,
516 FuncType::new(
517 [ValueType::String],
518 [ValueType::Result(my_codec_result.clone())],
519 ),
520 move |ctx, args, results| {
521 let [Value::String(config)] = args else {
522 anyhow::bail!("invalid numcodecs:abc/registry#get-external-codec arguments");
523 };
524
525 let [result] = results else {
526 anyhow::bail!("invalid numcodecs:abc/registry#get-external-codec results");
527 };
528
529 let res = match registry.get_codec(&mut serde_json::Deserializer::from_str(config)) {
530 Ok(codec) => Ok(Some(Value::Own(ResourceOwn::new(
531 ctx,
532 codec,
533 my_numcodecs_registry_codec_resource.clone(),
534 )?))),
535 Err(err) => Err(Some(Value::Record(into_wit_error(
536 err,
537 &my_numcodecs_types_error_record,
538 )?))),
539 };
540
541 *result = Value::Result(ResultValue::new(my_codec_result.clone(), res)?);
542
543 Ok(())
544 },
545 );
546 numcodecs_registry_instance.define_func("get-external-codec", get_external_codec)?;
547
548 Ok(())
549}
550
551fn into_wit_error<T: Error>(err: T, ty: &RecordType) -> Result<Record, anyhow::Error> {
552 let mut source: Option<&dyn Error> = err.source();
553
554 let message = Value::String(Arc::from(format!("{err}")));
555 let mut chain = if source.is_some() {
556 Vec::with_capacity(4)
557 } else {
558 Vec::new()
559 };
560
561 while let Some(err) = source.take() {
562 chain.push(Value::String(Arc::from(format!("{err}"))));
563 source = err.source();
564 }
565
566 Record::new(
567 ty.clone(),
568 [
569 ("message", message),
570 (
571 "chain",
572 Value::List(List::new(ListType::new(ValueType::String), chain)?),
573 ),
574 ],
575 )
576}