1use crate::{CompileTimeBuiltin, KeyValuePair, UnsafeIntrinsicsImport, WasiNnGraph};
9#[cfg(feature = "clap")]
10use clap::builder::{StringValueParser, TypedValueParser, ValueParserFactory};
11#[cfg(feature = "clap")]
12use clap::error::{Error, ErrorKind};
13#[cfg(feature = "serde")]
14use serde::de::{self, Visitor};
15use std::fmt;
16use std::num::{NonZeroU32, NonZeroUsize};
17use std::path::{Path, PathBuf};
18use std::str::FromStr;
19use std::time::Duration;
20use wasmtime::error::Context;
21use wasmtime::{Result, bail, format_err};
22
23const IGNORED_NUMBER_CHARS: [char; 1] = ['_'];
25
26#[macro_export]
27macro_rules! wasmtime_option_group {
28 (
29 #[env = $env:tt]
30 $(#[$attr:meta])*
31 pub struct $opts:ident {
32 $(
33 $(#[doc = $doc:tt])*
34 $(#[doc($doc_attr:meta)])?
35 $(#[serde($serde_attr:meta)])*
36 pub $opt:ident: $container:ident<$payload:ty>,
37 )+
38
39 $(
40 #[prefixed = $prefix:tt]
41 $(#[serde($serde_attr2:meta)])*
42 $(#[doc = $prefixed_doc:tt])*
43 $(#[doc($prefixed_doc_attr:meta)])?
44 pub $prefixed:ident: Vec<(String, Option<String>)>,
45 )?
46 }
47 enum $option:ident {
48 ...
49 }
50 ) => {
51 #[derive(Default, Debug, PartialEq, Clone)]
52 #[cfg_attr(feature = "serde", derive(serde_derive::Deserialize, serde_derive::Serialize))]
53 #[cfg_attr(feature = "serde", serde(rename_all = "kebab-case", deny_unknown_fields))]
54 $(#[$attr])*
55 pub struct $opts {
56 $(
57 $(#[cfg_attr(feature = "serde", serde($serde_attr))])*
58 $(#[doc($doc_attr)])?
59 pub $opt: $container<$payload>,
60 )+
61 $(
62 $(#[cfg_attr(feature = "serde", serde($serde_attr2))])*
63 pub $prefixed: Vec<(String, Option<String>)>,
64 )?
65 }
66
67 #[derive(Clone, PartialEq)]
68 #[expect(non_camel_case_types, reason = "macro-generated code")]
69 enum $option {
70 $(
71 $opt($payload),
72 )+
73 $(
74 $prefixed(String, Option<String>),
75 )?
76 }
77
78 impl $crate::opt::WasmtimeOption for $option {
79 const ENV_PREFIX: &'static str = concat!("WASMTIME_", $env);
80 const OPTIONS: &'static [$crate::opt::OptionDesc<$option>] = &[
81 $(
82 $crate::opt::OptionDesc {
83 name: $crate::opt::OptName::Name(stringify!($opt)),
84 parse: |_, s| {
85 Ok($option::$opt(
86 $crate::opt::WasmtimeOptionValue::parse(s)?
87 ))
88 },
89 val_help: <$payload as $crate::opt::WasmtimeOptionValue>::VAL_HELP,
90 docs: concat!($($doc, "\n",)*),
91 },
92 )+
93 $(
94 $crate::opt::OptionDesc {
95 name: $crate::opt::OptName::Prefix($prefix),
96 parse: |name, val| {
97 Ok($option::$prefixed(
98 name.to_string(),
99 val.map(|v| v.to_string()),
100 ))
101 },
102 val_help: "[=val]",
103 docs: concat!($($prefixed_doc, "\n",)*),
104 },
105 )?
106 ];
107 }
108
109 impl core::fmt::Display for $option {
110 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
111 match self {
112 $(
113 $option::$opt(val) => {
114 write!(f, "{}=", stringify!($opt).replace('_', "-"))?;
115 $crate::opt::WasmtimeOptionValue::display(val, f)
116 }
117 )+
118 $(
119 $option::$prefixed(key, val) => {
120 write!(f, "{}-{key}", stringify!($prefixed))?;
121 if let Some(val) = val {
122 write!(f, "={val}")?;
123 }
124 Ok(())
125 }
126 )?
127 }
128 }
129 }
130
131 impl $opts {
132 fn configure_with(&mut self, opts: &[$crate::opt::CommaSeparated<$option>]) -> Result<()> {
133 let env_opts = <$option as $crate::opt::WasmtimeOption>::parse_env()?;
134 for opt in env_opts.iter().chain(opts.iter().flat_map(|o| o.0.iter())) {
135 self.configure(opt);
136 }
137 Ok(())
138 }
139
140 fn configure(&mut self, opt: &$option) {
141 match opt {
142 $(
143 $option::$opt(val) => {
144 $crate::opt::OptionContainer::push(&mut self.$opt, val.clone());
145 }
146 )+
147 $(
148 $option::$prefixed(key, val) => self.$prefixed.push((key.clone(), val.clone())),
149 )?
150 }
151 }
152
153 fn to_options(&self) -> Vec<$option> {
154 let mut ret = Vec::new();
155 $(
156 for item in $crate::opt::OptionContainer::get(&self.$opt) {
157 ret.push($option::$opt(item.clone()));
158 }
159 )+
160 $(
161 for (key,val) in self.$prefixed.iter() {
162 ret.push($option::$prefixed(key.clone(), val.clone()));
163 }
164 )?
165 ret
166 }
167 }
168 };
169}
170
171#[derive(Clone, Debug, PartialEq)]
173pub struct CommaSeparated<T>(pub Vec<T>);
174
175#[cfg(feature = "clap")]
176impl<T> ValueParserFactory for CommaSeparated<T>
177where
178 T: WasmtimeOption,
179{
180 type Parser = CommaSeparatedParser<T>;
181
182 fn value_parser() -> CommaSeparatedParser<T> {
183 CommaSeparatedParser(std::marker::PhantomData)
184 }
185}
186
187#[derive(Clone)]
188#[cfg(feature = "clap")]
189pub struct CommaSeparatedParser<T>(std::marker::PhantomData<T>);
190
191#[cfg(feature = "clap")]
192impl<T> TypedValueParser for CommaSeparatedParser<T>
193where
194 T: WasmtimeOption,
195{
196 type Value = CommaSeparated<T>;
197
198 fn parse_ref(
199 &self,
200 cmd: &clap::Command,
201 arg: Option<&clap::Arg>,
202 value: &std::ffi::OsStr,
203 ) -> Result<Self::Value, Error> {
204 let val = StringValueParser::new().parse_ref(cmd, arg, value)?;
205
206 let options = T::OPTIONS;
207 let arg = arg.expect("should always have an argument");
208 let arg_long = arg.get_long().expect("should have a long name specified");
209 let arg_short = arg.get_short().expect("should have a short name specified");
210
211 if val == "help" {
214 let mut max = 0;
215 for d in options {
216 max = max.max(d.name.display_string().len() + d.val_help.len());
217 }
218 println!("Available {arg_long} options:\n");
219 for d in options {
220 print!(
221 " -{arg_short} {:>1$}",
222 d.name.display_string(),
223 max - d.val_help.len()
224 );
225 print!("{}", d.val_help);
226 print!(" --");
227 if val == "help" {
228 for line in d.docs.lines().map(|s| s.trim()) {
229 if line.is_empty() {
230 break;
231 }
232 print!(" {line}");
233 }
234 println!();
235 } else {
236 println!();
237 for line in d.docs.lines().map(|s| s.trim()) {
238 let line = line.trim();
239 println!(" {line}");
240 }
241 }
242 }
243 println!("\npass `-{arg_short} help-long` to see longer-form explanations");
244 std::process::exit(0);
245 }
246 if val == "help-long" {
247 println!("Available {arg_long} options:\n");
248 for d in options {
249 println!(
250 " -{arg_short} {}{} --",
251 d.name.display_string(),
252 d.val_help
253 );
254 println!();
255 for line in d.docs.lines().map(|s| s.trim()) {
256 let line = line.trim();
257 println!(" {line}");
258 }
259 }
260 std::process::exit(0);
261 }
262
263 T::parse_csv(&val).map(CommaSeparated).map_err(|e| {
264 Error::raw(
265 ErrorKind::InvalidValue,
266 format!("failed to parse -{arg_short} / --{arg_long} option: {e:?}\n"),
267 )
268 })
269 }
270}
271
272pub trait WasmtimeOption: Sized + Send + Sync + Clone + 'static {
275 const OPTIONS: &'static [OptionDesc<Self>];
276 const ENV_PREFIX: &'static str;
277
278 fn parse_env() -> Result<Vec<Self>> {
284 let mut ret = Vec::new();
285 if let Some(val) = std::env::var_os(Self::ENV_PREFIX) {
286 let val = match val.to_str() {
287 Some(s) => s,
288 None => bail!("env var `{}` is not valid UTF-8", Self::ENV_PREFIX),
289 };
290 ret.extend(
291 Self::parse_csv(&val)
292 .with_context(|| format!("failed to parse env var `{}`", Self::ENV_PREFIX))?,
293 );
294 }
295
296 for option in Self::OPTIONS {
297 let key = match &option.name {
298 OptName::Name(s) => format!("{}_{}", Self::ENV_PREFIX, s.to_ascii_uppercase()),
299 OptName::Prefix(_) => continue,
300 };
301 let val = match std::env::var_os(&key) {
302 Some(val) => val,
303 None => continue,
304 };
305 let val = match val.to_str() {
306 Some(s) => s,
307 None => bail!("env var `{key}` is not valid UTF-8"),
308 };
309 ret.push(
310 (option.parse)(&key, Some(val))
311 .with_context(|| format!("failed to parse env var `{key}`"))?,
312 );
313 }
314 Ok(ret)
315 }
316
317 fn parse_csv(val: &str) -> Result<Vec<Self>> {
319 let mut result = Vec::new();
320 for val in val.split(',') {
321 let mut iter = val.splitn(2, '=');
323 let key = iter.next().unwrap();
324 let key_val = iter.next();
325
326 let option = Self::OPTIONS
328 .iter()
329 .filter_map(|d| match d.name {
330 OptName::Name(s) => {
331 let s = s.replace('_', "-");
332 if s == key { Some((d, s)) } else { None }
333 }
334 OptName::Prefix(s) => {
335 let name = key.strip_prefix(s)?.strip_prefix("-")?;
336 Some((d, name.to_string()))
337 }
338 })
339 .next();
340
341 let (desc, key) = match option {
342 Some(pair) => pair,
343 None => bail!("unknown option: {key}\n"),
344 };
345
346 result.push(
347 (desc.parse)(&key, key_val)
348 .with_context(|| format!("failed to parse option `{val}`"))?,
349 );
350 }
351 Ok(result)
352 }
353}
354
355pub struct OptionDesc<T> {
356 pub name: OptName,
357 pub docs: &'static str,
358 pub parse: fn(&str, Option<&str>) -> Result<T>,
359 pub val_help: &'static str,
360}
361
362pub enum OptName {
363 Name(&'static str),
366
367 Prefix(&'static str),
369}
370
371impl OptName {
372 #[cfg(feature = "clap")]
373 fn display_string(&self) -> String {
374 match self {
375 OptName::Name(s) => s.replace('_', "-"),
376 OptName::Prefix(s) => format!("{s}-<KEY>"),
377 }
378 }
379}
380
381pub trait WasmtimeOptionValue: Sized {
384 const VAL_HELP: &'static str;
386
387 fn parse(val: Option<&str>) -> Result<Self>;
389
390 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
392}
393
394impl WasmtimeOptionValue for String {
395 const VAL_HELP: &'static str = "=val";
396 fn parse(val: Option<&str>) -> Result<Self> {
397 match val {
398 Some(val) => Ok(val.to_string()),
399 None => bail!("value must be specified with `key=val` syntax"),
400 }
401 }
402
403 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 f.write_str(self)
405 }
406}
407
408impl WasmtimeOptionValue for PathBuf {
409 const VAL_HELP: &'static str = "=path";
410 fn parse(val: Option<&str>) -> Result<Self> {
411 match val {
412 Some(val) => Ok(PathBuf::from_str(val)?),
413 None => bail!("value must be specified with key=val syntax"),
414 }
415 }
416
417 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418 write!(f, "{self:?}")
419 }
420}
421
422impl WasmtimeOptionValue for u32 {
423 const VAL_HELP: &'static str = "=N";
424 fn parse(val: Option<&str>) -> Result<Self> {
425 let val = String::parse(val)?.replace(IGNORED_NUMBER_CHARS, "");
426 match val.strip_prefix("0x") {
427 Some(hex) => Ok(u32::from_str_radix(hex, 16)?),
428 None => Ok(val.parse()?),
429 }
430 }
431
432 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433 write!(f, "{self}")
434 }
435}
436
437impl WasmtimeOptionValue for NonZeroU32 {
438 const VAL_HELP: &'static str = "=N";
439
440 fn parse(val: Option<&str>) -> Result<Self> {
441 let n = <u32 as WasmtimeOptionValue>::parse(val)?;
442 NonZeroU32::new(n).ok_or_else(|| format_err!("value must be non-zero"))
443 }
444
445 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446 write!(f, "{self}")
447 }
448}
449
450impl WasmtimeOptionValue for NonZeroUsize {
451 const VAL_HELP: &'static str = "=N";
452
453 fn parse(val: Option<&str>) -> Result<Self> {
454 let n = <usize as WasmtimeOptionValue>::parse(val)?;
455 NonZeroUsize::new(n).ok_or_else(|| format_err!("value must be non-zero"))
456 }
457
458 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
459 write!(f, "{self}")
460 }
461}
462
463impl WasmtimeOptionValue for u64 {
464 const VAL_HELP: &'static str = "=N";
465 fn parse(val: Option<&str>) -> Result<Self> {
466 let val = String::parse(val)?.replace(IGNORED_NUMBER_CHARS, "");
467 match val.strip_prefix("0x") {
468 Some(hex) => Ok(u64::from_str_radix(hex, 16)?),
469 None => Ok(val.parse()?),
470 }
471 }
472
473 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
474 write!(f, "{self}")
475 }
476}
477
478impl WasmtimeOptionValue for usize {
479 const VAL_HELP: &'static str = "=N";
480 fn parse(val: Option<&str>) -> Result<Self> {
481 let val = String::parse(val)?.replace(IGNORED_NUMBER_CHARS, "");
482 match val.strip_prefix("0x") {
483 Some(hex) => Ok(usize::from_str_radix(hex, 16)?),
484 None => Ok(val.parse()?),
485 }
486 }
487
488 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
489 write!(f, "{self}")
490 }
491}
492
493impl WasmtimeOptionValue for bool {
494 const VAL_HELP: &'static str = "[=y|n]";
495 fn parse(val: Option<&str>) -> Result<Self> {
496 match val {
497 None | Some("y") | Some("yes") | Some("true") => Ok(true),
498 Some("n") | Some("no") | Some("false") => Ok(false),
499 Some(s) => bail!("unknown boolean flag `{s}`, only yes,no,<nothing> accepted"),
500 }
501 }
502
503 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
504 if *self {
505 f.write_str("y")
506 } else {
507 f.write_str("n")
508 }
509 }
510}
511
512impl WasmtimeOptionValue for Duration {
513 const VAL_HELP: &'static str = "=N|Ns|Nms|..";
514 fn parse(val: Option<&str>) -> Result<Duration> {
515 let s = String::parse(val)?;
516 if let Ok(val) = s.parse() {
518 return Ok(Duration::from_secs(val));
519 }
520
521 if let Some(num) = s.strip_suffix("s") {
522 if let Ok(val) = num.parse() {
523 return Ok(Duration::from_secs(val));
524 }
525 }
526 if let Some(num) = s.strip_suffix("ms") {
527 if let Ok(val) = num.parse() {
528 return Ok(Duration::from_millis(val));
529 }
530 }
531 if let Some(num) = s.strip_suffix("us").or(s.strip_suffix("μs")) {
532 if let Ok(val) = num.parse() {
533 return Ok(Duration::from_micros(val));
534 }
535 }
536 if let Some(num) = s.strip_suffix("ns") {
537 if let Ok(val) = num.parse() {
538 return Ok(Duration::from_nanos(val));
539 }
540 }
541
542 bail!("failed to parse duration: {s}")
543 }
544
545 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
546 let subsec = self.subsec_nanos();
547 if subsec == 0 {
548 write!(f, "{}s", self.as_secs())
549 } else if subsec % 1_000 == 0 {
550 write!(f, "{}μs", self.as_micros())
551 } else if subsec % 1_000_000 == 0 {
552 write!(f, "{}ms", self.as_millis())
553 } else {
554 write!(f, "{}ns", self.as_nanos())
555 }
556 }
557}
558
559impl WasmtimeOptionValue for wasmtime::OptLevel {
560 const VAL_HELP: &'static str = "=0|1|2|s";
561 fn parse(val: Option<&str>) -> Result<Self> {
562 match String::parse(val)?.as_str() {
563 "0" => Ok(wasmtime::OptLevel::None),
564 "1" => Ok(wasmtime::OptLevel::Speed),
565 "2" => Ok(wasmtime::OptLevel::Speed),
566 "s" => Ok(wasmtime::OptLevel::SpeedAndSize),
567 other => bail!("unknown optimization level `{other}`, only 0,1,2,s accepted"),
568 }
569 }
570
571 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
572 match *self {
573 wasmtime::OptLevel::None => f.write_str("0"),
574 wasmtime::OptLevel::Speed => f.write_str("2"),
575 wasmtime::OptLevel::SpeedAndSize => f.write_str("s"),
576 _ => unreachable!(),
577 }
578 }
579}
580
581impl WasmtimeOptionValue for wasmtime::RegallocAlgorithm {
582 const VAL_HELP: &'static str = "=backtracking|single-pass";
583 fn parse(val: Option<&str>) -> Result<Self> {
584 match String::parse(val)?.as_str() {
585 "backtracking" => Ok(wasmtime::RegallocAlgorithm::Backtracking),
586 "single-pass" => Ok(wasmtime::RegallocAlgorithm::SinglePass),
587 other => {
588 bail!("unknown regalloc algorithm`{other}`, only backtracking,single-pass accepted")
589 }
590 }
591 }
592
593 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
594 match *self {
595 wasmtime::RegallocAlgorithm::Backtracking => f.write_str("backtracking"),
596 wasmtime::RegallocAlgorithm::SinglePass => f.write_str("single-pass"),
597 _ => unreachable!(),
598 }
599 }
600}
601
602impl WasmtimeOptionValue for wasmtime::Strategy {
603 const VAL_HELP: &'static str = "=winch|cranelift";
604 fn parse(val: Option<&str>) -> Result<Self> {
605 match String::parse(val)?.as_str() {
606 "cranelift" => Ok(wasmtime::Strategy::Cranelift),
607 "winch" => Ok(wasmtime::Strategy::Winch),
608 other => bail!("unknown compiler `{other}` only `cranelift` and `winch` accepted",),
609 }
610 }
611
612 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
613 match *self {
614 wasmtime::Strategy::Cranelift => f.write_str("cranelift"),
615 wasmtime::Strategy::Winch => f.write_str("winch"),
616 _ => unreachable!(),
617 }
618 }
619}
620
621impl WasmtimeOptionValue for wasmtime::Collector {
622 const VAL_HELP: &'static str = "=drc|null|copying";
623 fn parse(val: Option<&str>) -> Result<Self> {
624 match String::parse(val)?.as_str() {
625 "drc" => Ok(wasmtime::Collector::DeferredReferenceCounting),
626 "null" => Ok(wasmtime::Collector::Null),
627 "copying" => Ok(wasmtime::Collector::Copying),
628 other => {
629 bail!("unknown collector `{other}` only `drc`, `null`, and `copying` accepted",)
630 }
631 }
632 }
633
634 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
635 match *self {
636 wasmtime::Collector::DeferredReferenceCounting => f.write_str("drc"),
637 wasmtime::Collector::Null => f.write_str("null"),
638 wasmtime::Collector::Copying => f.write_str("copying"),
639 _ => unreachable!(),
640 }
641 }
642}
643
644impl WasmtimeOptionValue for wasmtime::Enabled {
645 const VAL_HELP: &'static str = "[=y|n|auto]";
646 fn parse(val: Option<&str>) -> Result<Self> {
647 match val {
648 None | Some("y") | Some("yes") | Some("true") => Ok(wasmtime::Enabled::Yes),
649 Some("n") | Some("no") | Some("false") => Ok(wasmtime::Enabled::No),
650 Some("auto") => Ok(wasmtime::Enabled::Auto),
651 Some(s) => bail!("unknown flag `{s}`, only yes,no,auto,<nothing> accepted"),
652 }
653 }
654
655 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
656 match *self {
657 wasmtime::Enabled::Yes => f.write_str("y"),
658 wasmtime::Enabled::No => f.write_str("n"),
659 wasmtime::Enabled::Auto => f.write_str("auto"),
660 }
661 }
662}
663
664impl WasmtimeOptionValue for wasmtime::Inlining {
665 const VAL_HELP: &'static str = "[=y|n|gc|inter-module|intrinsics]";
666 fn parse(val: Option<&str>) -> Result<Self> {
667 match val {
668 None => Ok(wasmtime::Inlining::Yes),
669 Some(val) => val.parse(),
670 }
671 }
672
673 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
674 write!(f, "{self}")
675 }
676}
677
678impl WasmtimeOptionValue for WasiNnGraph {
679 const VAL_HELP: &'static str = "=<format>::<dir>";
680 fn parse(val: Option<&str>) -> Result<Self> {
681 let val = String::parse(val)?;
682 let mut parts = val.splitn(2, "::");
683 Ok(WasiNnGraph {
684 format: parts.next().unwrap().to_string(),
685 dir: match parts.next() {
686 Some(part) => part.into(),
687 None => bail!("graph does not contain `::` separator for directory"),
688 },
689 })
690 }
691
692 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
693 write!(f, "{}::{}", self.format, self.dir)
694 }
695}
696
697impl WasmtimeOptionValue for KeyValuePair {
698 const VAL_HELP: &'static str = "=<name>=<val>";
699 fn parse(val: Option<&str>) -> Result<Self> {
700 let val = String::parse(val)?;
701 let mut parts = val.splitn(2, "=");
702 Ok(KeyValuePair {
703 key: parts.next().unwrap().to_string(),
704 value: match parts.next() {
705 Some(part) => part.into(),
706 None => "".to_string(),
707 },
708 })
709 }
710
711 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
712 f.write_str(&self.key)?;
713 if !self.value.is_empty() {
714 f.write_str("=")?;
715 f.write_str(&self.value)?;
716 }
717 Ok(())
718 }
719}
720
721impl WasmtimeOptionValue for UnsafeIntrinsicsImport {
722 const VAL_HELP: &'static str = "[=name]";
723 fn parse(val: Option<&str>) -> Result<Self> {
724 match val {
725 None => Ok(UnsafeIntrinsicsImport("unsafe-intrinsics".to_string())),
726 Some("") => bail!("the unsafe intrinsics import name cannot be empty"),
727 Some(val) => Ok(UnsafeIntrinsicsImport(val.to_string())),
728 }
729 }
730
731 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
732 f.write_str(&self.0)
733 }
734}
735
736impl WasmtimeOptionValue for CompileTimeBuiltin {
737 const VAL_HELP: &'static str = "=[<name>=]<path>";
738 fn parse(val: Option<&str>) -> Result<Self> {
739 let val = String::parse(val)?;
740
741 if let Some((name, path)) = val.split_once('=') {
742 if name.is_empty() {
743 bail!("the compile-time builtin's name cannot be empty in `{val}`");
744 }
745 if path.is_empty() {
746 bail!("the compile-time builtin's path cannot be empty in `{val}`");
747 }
748 return Ok(CompileTimeBuiltin {
749 name: name.to_string(),
750 path: PathBuf::from(path),
751 });
752 }
753
754 let path = PathBuf::from(&val);
757 let name = path
758 .file_stem()
759 .ok_or_else(|| format_err!("cannot derive a compile-time builtin name from `{val}`"))?
760 .to_str()
761 .ok_or_else(|| {
762 format_err!("compile-time builtin name derived from `{val}` is not valid UTF-8")
763 })?
764 .to_string();
765 if name.is_empty() {
766 bail!("compile-time builtin name derived from `{val}` is empty");
767 }
768 Ok(CompileTimeBuiltin { name, path })
769 }
770
771 fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
772 write!(f, "{}={}", self.name, Path::display(&self.path))
775 }
776}
777
778pub trait OptionContainer<T> {
779 fn push(&mut self, val: T);
780 fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
781 where
782 T: 'a;
783}
784
785impl<T> OptionContainer<T> for Option<T> {
786 fn push(&mut self, val: T) {
787 *self = Some(val);
788 }
789 fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
790 where
791 T: 'a,
792 {
793 self.iter()
794 }
795}
796
797impl<T> OptionContainer<T> for Vec<T> {
798 fn push(&mut self, val: T) {
799 Vec::push(self, val);
800 }
801 fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
802 where
803 T: 'a,
804 {
805 self.iter()
806 }
807}
808
809#[cfg(feature = "serde")]
814struct ToStringVisitor {}
815
816#[cfg(feature = "serde")]
817impl<'de> Visitor<'de> for ToStringVisitor {
818 type Value = String;
819
820 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
821 write!(formatter, "&str, u64, or i64")
822 }
823
824 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
825 where
826 E: de::Error,
827 {
828 Ok(s.to_owned())
829 }
830
831 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
832 where
833 E: de::Error,
834 {
835 Ok(v.to_string())
836 }
837
838 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
839 where
840 E: de::Error,
841 {
842 Ok(v.to_string())
843 }
844}
845
846#[cfg(feature = "serde")]
848pub(crate) fn deserialize_cli_parse_wrapper<'de, D, T>(
849 deserializer: D,
850) -> Result<Option<T>, D::Error>
851where
852 T: WasmtimeOptionValue,
853 D: serde::Deserializer<'de>,
854{
855 let to_string_visitor = ToStringVisitor {};
856 let str = deserializer.deserialize_any(to_string_visitor)?;
857
858 T::parse(Some(&str))
859 .map(Some)
860 .map_err(serde::de::Error::custom)
861}
862
863#[cfg(feature = "serde")]
864pub(crate) fn serialize_cli_parse_wrapper<S, T>(val: &Option<T>, ser: S) -> Result<S::Ok, S::Error>
865where
866 T: WasmtimeOptionValue,
867 S: serde::Serializer,
868{
869 match val {
870 Some(val) => ser.serialize_some(&fmt::from_fn(|f| val.display(f)).to_string()),
871 None => ser.serialize_none(),
872 }
873}
874
875#[cfg(test)]
876mod tests {
877 use super::WasmtimeOptionValue;
878
879 #[test]
880 fn numbers_with_underscores() {
881 assert!(<u32 as WasmtimeOptionValue>::parse(Some("123")).is_ok_and(|v| v == 123));
882 assert!(<u32 as WasmtimeOptionValue>::parse(Some("1_2_3")).is_ok_and(|v| v == 123));
883 }
884}