1use crate::{
2 functions::FunctionList,
3 types::{CargoDependency, Type, TypeIdent, TypeMap},
4};
5use std::{
6 collections::{BTreeMap, BTreeSet},
7 fmt::Display,
8 fs,
9};
10
11pub mod rust_plugin;
12pub mod rust_wasmer2_runtime;
13pub mod rust_wasmer2_wasi_runtime;
14pub mod ts_runtime;
15
16#[non_exhaustive]
17#[derive(Debug, Clone)]
18pub enum BindingsType {
19 RustPlugin(RustPluginConfig),
20 RustWasmer2Runtime,
21 RustWasmer2WasiRuntime,
22 TsRuntime(TsRuntimeConfig),
23}
24
25impl Display for BindingsType {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 f.write_str(match self {
28 BindingsType::RustPlugin { .. } => "rust-plugin",
29 BindingsType::RustWasmer2Runtime { .. } => "rust-wasmer2-runtime",
30 BindingsType::RustWasmer2WasiRuntime { .. } => "rust-wasmer2-wasi-runtime",
31 BindingsType::TsRuntime { .. } => "ts-runtime",
32 })
33 }
34}
35
36#[derive(Debug)]
37pub struct BindingConfig<'a> {
38 pub bindings_type: BindingsType,
39 pub path: &'a str,
40}
41
42#[non_exhaustive]
43#[derive(Debug, Clone)]
44pub struct RustPluginConfig {
45 pub name: Option<RustPluginConfigValue>,
47
48 pub authors: Option<RustPluginConfigValue>,
50
51 pub version: Option<RustPluginConfigValue>,
53
54 pub dependencies: BTreeMap<String, CargoDependency>,
62
63 pub description: Option<RustPluginConfigValue>,
65
66 pub readme: Option<RustPluginConfigValue>,
68
69 pub license: Option<RustPluginConfigValue>,
71}
72
73impl RustPluginConfig {
74 pub fn builder() -> RustPluginConfigBuilder {
75 RustPluginConfigBuilder {
76 config: RustPluginConfig {
77 name: None,
78 authors: None,
79 version: None,
80 dependencies: Default::default(),
81 description: None,
82 readme: None,
83 license: None,
84 },
85 }
86 }
87}
88
89#[derive(Debug, Clone)]
90#[non_exhaustive]
91pub enum RustPluginConfigValue {
92 String(String),
93 Vec(Vec<String>),
94 Workspace,
95}
96
97impl From<&str> for RustPluginConfigValue {
98 fn from(value: &str) -> Self {
99 Self::String(value.into())
100 }
101}
102
103impl From<String> for RustPluginConfigValue {
104 fn from(value: String) -> Self {
105 Self::String(value)
106 }
107}
108
109impl From<Vec<&str>> for RustPluginConfigValue {
110 fn from(value: Vec<&str>) -> Self {
111 Self::Vec(value.into_iter().map(|value| value.to_string()).collect())
112 }
113}
114
115impl From<Vec<String>> for RustPluginConfigValue {
116 fn from(value: Vec<String>) -> Self {
117 Self::Vec(value)
118 }
119}
120
121pub struct RustPluginConfigBuilder {
122 config: RustPluginConfig,
123}
124
125impl RustPluginConfigBuilder {
126 pub fn name(mut self, value: impl Into<String>) -> Self {
127 self.config.name = Some(RustPluginConfigValue::String(value.into()));
128 self
129 }
130
131 pub fn version(mut self, value: impl Into<RustPluginConfigValue>) -> Self {
132 self.config.version = Some(value.into());
133 self
134 }
135
136 pub fn authors(mut self, value: impl Into<RustPluginConfigValue>) -> Self {
137 self.config.authors = Some(value.into());
138 self
139 }
140
141 pub fn author(mut self, value: impl Into<String>) -> Self {
142 match &mut self.config.authors {
143 Some(RustPluginConfigValue::Vec(vec)) => {
144 vec.push(value.into());
145 }
146 None => {
147 self.config.authors = Some(RustPluginConfigValue::Vec(vec![value.into()]));
148 }
149 _ => panic!("Cannot add an author to a non-vector 'authors' field"),
150 }
151 self
152 }
153
154 pub fn description(mut self, value: impl Into<RustPluginConfigValue>) -> Self {
155 self.config.description = Some(value.into());
156 self
157 }
158
159 pub fn readme(mut self, value: impl Into<RustPluginConfigValue>) -> Self {
160 self.config.readme = Some(value.into());
161 self
162 }
163
164 pub fn license(mut self, value: impl Into<RustPluginConfigValue>) -> Self {
165 self.config.license = Some(value.into());
166 self
167 }
168
169 pub fn dependencies<'a>(
170 mut self,
171 value: impl Into<BTreeMap<&'a str, CargoDependency>>,
172 ) -> Self {
173 let dependencies = value.into();
174 self.config.dependencies = dependencies
175 .into_iter()
176 .map(|(key, value)| (key.to_string(), value))
177 .collect();
178 self
179 }
180
181 pub fn dependency(mut self, name: impl Into<String>, dependency: CargoDependency) -> Self {
182 self.config.dependencies.insert(name.into(), dependency);
183 self
184 }
185
186 pub fn build(self) -> RustPluginConfig {
187 assert!(
188 self.config.name.is_some(),
189 "'name' is required in RustPluginConfig"
190 );
191 self.config
192 }
193}
194
195#[non_exhaustive]
196#[derive(Debug, Clone)]
197pub struct TsRuntimeConfig {
198 pub msgpack_module: String,
204
205 pub generate_raw_export_wrappers: bool,
215
216 pub streaming_instantiation: bool,
228}
229
230impl TsRuntimeConfig {
231 pub fn new() -> Self {
233 Self::default()
234 }
235
236 pub fn with_msgpack_module(mut self, msgpack_module: &str) -> Self {
238 self.msgpack_module = msgpack_module.to_owned();
239 self
240 }
241
242 pub fn with_raw_export_wrappers(mut self) -> Self {
244 self.generate_raw_export_wrappers = true;
245 self
246 }
247
248 pub fn without_streaming_instantiation(mut self) -> Self {
250 self.streaming_instantiation = false;
251 self
252 }
253}
254
255impl Default for TsRuntimeConfig {
256 fn default() -> Self {
257 Self {
258 generate_raw_export_wrappers: false,
259 msgpack_module: "@msgpack/msgpack".to_owned(),
260 streaming_instantiation: true,
261 }
262 }
263}
264
265impl TsRuntimeConfig {}
266
267pub fn generate_bindings(
268 import_functions: FunctionList,
269 export_functions: FunctionList,
270 types: TypeMap,
271 config: BindingConfig,
272) {
273 fs::create_dir_all(config.path).expect("Could not create output directory");
274
275 display_warnings(&import_functions, &export_functions, &types);
276
277 match config.bindings_type {
278 BindingsType::RustPlugin(plugin_config) => rust_plugin::generate_bindings(
279 import_functions,
280 export_functions,
281 types,
282 plugin_config,
283 config.path,
284 ),
285 BindingsType::RustWasmer2Runtime => rust_wasmer2_runtime::generate_bindings(
286 import_functions,
287 export_functions,
288 types,
289 config.path,
290 ),
291 BindingsType::RustWasmer2WasiRuntime => rust_wasmer2_wasi_runtime::generate_bindings(
292 import_functions,
293 export_functions,
294 types,
295 config.path,
296 ),
297 BindingsType::TsRuntime(runtime_config) => ts_runtime::generate_bindings(
298 import_functions,
299 export_functions,
300 types,
301 runtime_config,
302 config.path,
303 ),
304 };
305}
306
307fn display_warnings(
308 import_functions: &FunctionList,
309 export_functions: &FunctionList,
310 types: &TypeMap,
311) {
312 let all_functions = import_functions.iter().chain(export_functions.iter());
313 let all_function_signature_types = all_functions.flat_map(|func| {
314 func.args
315 .iter()
316 .map(|arg| &arg.ty)
317 .chain(func.return_type.iter())
318 });
319 warn_about_custom_serializer_usage(
320 all_function_signature_types.clone(),
321 "function signature",
322 types,
323 );
324
325 let all_idents = all_function_signature_types
328 .chain(
329 types
330 .values()
331 .filter_map(|ty| match ty {
332 Type::Struct(ty) => Some(ty),
333 _ => None,
334 })
335 .flat_map(|ty| ty.fields.iter().map(|field| &field.ty)),
336 )
337 .chain(
338 types
339 .values()
340 .filter_map(|ty| match ty {
341 Type::Enum(ty) => Some(ty),
342 _ => None,
343 })
344 .flat_map(|ty| ty.variants.iter())
345 .filter_map(|variant| match &variant.ty {
346 Type::Struct(ty) => Some(ty),
347 _ => None,
348 })
349 .flat_map(|ty| ty.fields.iter().map(|field| &field.ty)),
350 );
351 warn_about_custom_serializer_usage(
352 all_idents.flat_map(|ident| ident.generic_args.iter().map(|(arg, _)| arg)),
353 "generic argument",
354 types,
355 );
356}
357
358fn warn_about_custom_serializer_usage<'a, T>(idents: T, context: &str, types: &TypeMap)
359where
360 T: Iterator<Item = &'a TypeIdent>,
361{
362 let mut idents_with_custom_serializers = BTreeSet::new();
363
364 for ident in idents {
365 let ty = types.get(ident);
366 if let Some(Type::Custom(ty)) = ty {
367 if ty.serde_attrs.iter().any(|attr| {
368 attr.starts_with("with = ")
369 || attr.starts_with("serialize_with = ")
370 || attr.starts_with("deserialize_with = ")
371 }) {
372 idents_with_custom_serializers.insert(ident);
373 }
374 }
375 }
376
377 for ident in idents_with_custom_serializers {
378 println!(
379 "WARNING: Type `{ident}` is used directly in a {context}, but relies on a custom Serde \
380 (de)serializer. This (de)serializer is NOT used when using the type directly \
381 in a {context}. This may result in unexpected (de)serialization issues, for instance \
382 when passing data between Rust and TypeScript.\n\
383 You may wish to create a newtype to avoid this warning.\n\
384 See `examples/example-protocol/src/types/time.rs` for an example."
385 );
386 }
387}