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(all(feature = "registry", target_arch = "wasm32"))]
39mod external;
40
41#[cfg(target_arch = "wasm32")]
42use crate::convert::{
43    from_wit_any_array, into_wit_any_array, into_wit_error, zeros_from_wit_any_array_prototype,
44};
45
46#[doc(hidden)]
47#[expect(clippy::same_length_and_capacity)]
48pub mod bindings {
49    #[cfg(not(feature = "registry"))]
50    wit_bindgen::generate!({
51        world: "numcodecs:abc/exports@0.1.1",
52        with: {
53            "numcodecs:abc/codec@0.1.1": generate,
54        },
55        pub_export_macro: true,
56    });
57    #[cfg(feature = "registry")]
58    wit_bindgen::generate!({
59        world: "numcodecs:abc/exports@0.1.1",
60        with: {
61            "numcodecs:abc/codec@0.1.1": generate,
62            // TODO: generate the separate types interface
63            "numcodecs:abc/types@0.1.1": crate::bindings::exports::numcodecs::abc::codec,
64        },
65        pub_export_macro: true,
66        features: ["registry"],
67    });
68}
69
70#[cfg(target_arch = "wasm32")]
71mod wit {
72    pub mod codec {
73        pub use crate::bindings::exports::numcodecs::abc::codec::{Codec, Guest, GuestCodec};
74    }
75
76    #[cfg(feature = "registry")]
77    pub mod registry {
78        pub use crate::bindings::numcodecs::abc::registry::{
79            ExternalCodec, ExternalCodecType, get_external_codec,
80        };
81    }
82
83    pub mod types {
84        // TODO: use crate::bindings::numcodecs::abc::types
85        pub use crate::bindings::exports::numcodecs::abc::codec::{
86            AnyArray, AnyArrayData, AnyArrayDtype, AnyArrayPrototype, Error, Json, JsonSchema,
87            Usize,
88        };
89    }
90}
91
92#[macro_export]
93/// Export a [`StaticCodec`] type using the WASM component model.
94///
95/// ```rust,ignore
96/// # use numcodecs_wasm_guest::export_codec;
97///
98/// struct MyCodec {
99///     // ...
100/// }
101///
102/// impl numcodecs::Codec for MyCodec {
103///     // ...
104/// }
105///
106/// impl numcodecs::StaticCodec for MyCodec {
107///     // ...
108/// }
109///
110/// export_codec!(MyCodec);
111/// ```
112macro_rules! export_codec {
113    ($codec:ty) => {
114        #[cfg(target_arch = "wasm32")]
115        const _: () = {
116            type Codec = $codec;
117
118            $crate::bindings::export!(
119                Codec with_types_in $crate::bindings
120            );
121        };
122
123        const _: () = {
124            const fn can_only_export_static_codec<T: $crate::numcodecs::StaticCodec>() {}
125
126            can_only_export_static_codec::<$codec>()
127        };
128    };
129}
130
131#[cfg(target_arch = "wasm32")]
132#[doc(hidden)]
133impl<T: StaticCodec> wit::codec::Guest for T {
134    type Codec = Self;
135
136    fn codec_id() -> String {
137        String::from(<Self as StaticCodec>::CODEC_ID)
138    }
139
140    fn codec_config_schema() -> wit::types::JsonSchema {
141        schema_for!(<Self as StaticCodec>::Config<'static>)
142            .as_value()
143            .to_string()
144    }
145}
146
147#[cfg(target_arch = "wasm32")]
148impl<T: StaticCodec> wit::codec::GuestCodec for T {
149    fn from_config(config: String) -> Result<wit::codec::Codec, wit::types::Error> {
150        let err = match <Self as StaticCodec>::Config::deserialize(
151            &mut serde_json::Deserializer::from_str(&config),
152        ) {
153            Ok(config) => {
154                return Ok(wit::codec::Codec::new(<Self as StaticCodec>::from_config(
155                    config,
156                )));
157            }
158            Err(err) => err,
159        };
160
161        let err = format_serde_error::SerdeError::new(config, err);
162        Err(into_wit_error(err))
163    }
164
165    fn encode(
166        &self,
167        data: wit::types::AnyArray,
168    ) -> Result<wit::types::AnyArray, wit::types::Error> {
169        let data = match from_wit_any_array(data) {
170            Ok(data) => data,
171            Err(err) => return Err(into_wit_error(err)),
172        };
173
174        match <Self as Codec>::encode(self, data.into_cow()) {
175            Ok(encoded) => match into_wit_any_array(encoded) {
176                Ok(encoded) => Ok(encoded),
177                Err(err) => Err(into_wit_error(err)),
178            },
179            Err(err) => Err(into_wit_error(err)),
180        }
181    }
182
183    fn decode(
184        &self,
185        encoded: wit::types::AnyArray,
186    ) -> Result<wit::types::AnyArray, wit::types::Error> {
187        let encoded = match from_wit_any_array(encoded) {
188            Ok(encoded) => encoded,
189            Err(err) => return Err(into_wit_error(err)),
190        };
191
192        match <Self as Codec>::decode(self, encoded.into_cow()) {
193            Ok(decoded) => match into_wit_any_array(decoded) {
194                Ok(decoded) => Ok(decoded),
195                Err(err) => Err(into_wit_error(err)),
196            },
197            Err(err) => Err(into_wit_error(err)),
198        }
199    }
200
201    fn decode_into(
202        &self,
203        encoded: wit::types::AnyArray,
204        decoded: wit::types::AnyArrayPrototype,
205    ) -> Result<wit::types::AnyArray, wit::types::Error> {
206        let encoded = match from_wit_any_array(encoded) {
207            Ok(encoded) => encoded,
208            Err(err) => return Err(into_wit_error(err)),
209        };
210
211        let mut decoded = zeros_from_wit_any_array_prototype(decoded);
212
213        match <Self as Codec>::decode_into(self, encoded.view(), decoded.view_mut()) {
214            Ok(()) => match into_wit_any_array(decoded) {
215                Ok(decoded) => Ok(decoded),
216                Err(err) => Err(into_wit_error(err)),
217            },
218            Err(err) => Err(into_wit_error(err)),
219        }
220    }
221
222    fn get_config(&self) -> Result<wit::types::Json, wit::types::Error> {
223        match serde_json::to_string(&<Self as StaticCodec>::get_config(self)) {
224            Ok(config) => Ok(config),
225            Err(err) => Err(into_wit_error(err)),
226        }
227    }
228}