Skip to main content

numcodecs_wasm_host/
registry.rs

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/// Adds the `registry` to the `linker` to define the `numcodecs:abc/registry`
13/// interface.
14///
15/// # Errors
16///
17/// Errors if adding the `registry` to the `linker` fails.
18#[expect(clippy::too_many_lines)] // FIXME
19pub 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, // skip name to keep plain data types flexible
33        [
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}