Skip to main content

numcodecs_wasm_guest/
lib.rs

1//! [![CI Status]][workflow] [![MSRV]][repo] [![Latest Version]][crates.io] [![Rust Doc Crate]][docs.rs] [![Rust Doc Main]][docs]
2//!
3//! [CI Status]: https://img.shields.io/github/actions/workflow/status/juntyr/numcodecs-rs/ci.yml?branch=main
4//! [workflow]: https://github.com/juntyr/numcodecs-rs/actions/workflows/ci.yml?query=branch%3Amain
5//!
6//! [MSRV]: https://img.shields.io/badge/MSRV-1.87.0-blue
7//! [repo]: https://github.com/juntyr/numcodecs-rs
8//!
9//! [Latest Version]: https://img.shields.io/crates/v/numcodecs-wasm-guest
10//! [crates.io]: https://crates.io/crates/numcodecs-wasm-guest
11//!
12//! [Rust Doc Crate]: https://img.shields.io/docsrs/numcodecs-wasm-guest
13//! [docs.rs]: https://docs.rs/numcodecs-wasm-guest/
14//!
15//! [Rust Doc Main]: https://img.shields.io/badge/docs-main-blue
16//! [docs]: https://juntyr.github.io/numcodecs-rs/numcodecs_wasm_guest
17//!
18//! wasm32 guest-side bindings for the [`numcodecs`] API, which allows you to
19//! export one [`StaticCodec`] from a WASM component.
20
21// Required in docs and the [`export_codec`] macro
22#[doc(hidden)]
23pub use numcodecs;
24
25#[cfg(doc)]
26use numcodecs::StaticCodec;
27
28#[cfg(target_arch = "wasm32")]
29use ::{
30    numcodecs::{Codec, StaticCodec},
31    schemars::schema_for,
32    serde::Deserialize,
33};
34
35#[cfg(target_arch = "wasm32")]
36mod convert;
37
38#[cfg(target_arch = "wasm32")]
39use crate::{
40    bindings::exports::numcodecs::abc::codec as wit,
41    convert::{
42        from_wit_any_array, into_wit_any_array, into_wit_error, zeros_from_wit_any_array_prototype,
43    },
44};
45
46#[doc(hidden)]
47#[expect(clippy::same_length_and_capacity)]
48pub mod bindings {
49    wit_bindgen::generate!({
50        world: "numcodecs:abc/exports@0.1.1",
51        with: {
52            "numcodecs:abc/codec@0.1.1": generate,
53        },
54        pub_export_macro: true,
55    });
56}
57
58#[macro_export]
59/// Export a [`StaticCodec`] type using the WASM component model.
60///
61/// ```rust,ignore
62/// # use numcodecs_wasm_guest::export_codec;
63///
64/// struct MyCodec {
65///     // ...
66/// }
67///
68/// impl numcodecs::Codec for MyCodec {
69///     // ...
70/// }
71///
72/// impl numcodecs::StaticCodec for MyCodec {
73///     // ...
74/// }
75///
76/// export_codec!(MyCodec);
77/// ```
78macro_rules! export_codec {
79    ($codec:ty) => {
80        #[cfg(target_arch = "wasm32")]
81        const _: () = {
82            type Codec = $codec;
83
84            $crate::bindings::export!(
85                Codec with_types_in $crate::bindings
86            );
87        };
88
89        const _: () = {
90            const fn can_only_export_static_codec<T: $crate::numcodecs::StaticCodec>() {}
91
92            can_only_export_static_codec::<$codec>()
93        };
94    };
95}
96
97#[cfg(target_arch = "wasm32")]
98#[doc(hidden)]
99impl<T: StaticCodec> wit::Guest for T {
100    type Codec = Self;
101
102    fn codec_id() -> String {
103        String::from(<Self as StaticCodec>::CODEC_ID)
104    }
105
106    fn codec_config_schema() -> wit::JsonSchema {
107        schema_for!(<Self as StaticCodec>::Config<'static>)
108            .as_value()
109            .to_string()
110    }
111}
112
113#[cfg(target_arch = "wasm32")]
114impl<T: StaticCodec> wit::GuestCodec for T {
115    fn from_config(config: String) -> Result<wit::Codec, wit::Error> {
116        let err = match <Self as StaticCodec>::Config::deserialize(
117            &mut serde_json::Deserializer::from_str(&config),
118        ) {
119            Ok(config) => return Ok(wit::Codec::new(<Self as StaticCodec>::from_config(config))),
120            Err(err) => err,
121        };
122
123        let err = format_serde_error::SerdeError::new(config, err);
124        Err(into_wit_error(err))
125    }
126
127    fn encode(&self, data: wit::AnyArray) -> Result<wit::AnyArray, wit::Error> {
128        let data = match from_wit_any_array(data) {
129            Ok(data) => data,
130            Err(err) => return Err(into_wit_error(err)),
131        };
132
133        match <Self as Codec>::encode(self, data.into_cow()) {
134            Ok(encoded) => match into_wit_any_array(encoded) {
135                Ok(encoded) => Ok(encoded),
136                Err(err) => Err(into_wit_error(err)),
137            },
138            Err(err) => Err(into_wit_error(err)),
139        }
140    }
141
142    fn decode(&self, encoded: wit::AnyArray) -> Result<wit::AnyArray, wit::Error> {
143        let encoded = match from_wit_any_array(encoded) {
144            Ok(encoded) => encoded,
145            Err(err) => return Err(into_wit_error(err)),
146        };
147
148        match <Self as Codec>::decode(self, encoded.into_cow()) {
149            Ok(decoded) => match into_wit_any_array(decoded) {
150                Ok(decoded) => Ok(decoded),
151                Err(err) => Err(into_wit_error(err)),
152            },
153            Err(err) => Err(into_wit_error(err)),
154        }
155    }
156
157    fn decode_into(
158        &self,
159        encoded: wit::AnyArray,
160        decoded: wit::AnyArrayPrototype,
161    ) -> Result<wit::AnyArray, wit::Error> {
162        let encoded = match from_wit_any_array(encoded) {
163            Ok(encoded) => encoded,
164            Err(err) => return Err(into_wit_error(err)),
165        };
166
167        let mut decoded = zeros_from_wit_any_array_prototype(decoded);
168
169        match <Self as Codec>::decode_into(self, encoded.view(), decoded.view_mut()) {
170            Ok(()) => match into_wit_any_array(decoded) {
171                Ok(decoded) => Ok(decoded),
172                Err(err) => Err(into_wit_error(err)),
173            },
174            Err(err) => Err(into_wit_error(err)),
175        }
176    }
177
178    fn get_config(&self) -> Result<wit::Json, wit::Error> {
179        match serde_json::to_string(&<Self as StaticCodec>::get_config(self)) {
180            Ok(config) => Ok(config),
181            Err(err) => Err(into_wit_error(err)),
182        }
183    }
184}