1use std::{fmt, path::PathBuf, str::FromStr};
2
3use crate::{CliError, CratesIoSpec};
4use sim_kernel::{LibSourceSpec as KernelLibSourceSpec, Symbol};
5
6#[derive(Clone, Debug, PartialEq, Eq)]
22pub enum LibSourceSpec {
23 Symbol(String),
25 Path(PathBuf),
27 Url(String),
29 Bytes(Vec<u8>),
31 Host(String),
33 CratesIo(CratesIoSpec),
35}
36
37impl LibSourceSpec {
38 pub(crate) fn to_kernel_data_source(&self) -> Option<KernelLibSourceSpec> {
39 match self {
40 Self::Symbol(symbol) => Some(KernelLibSourceSpec::Symbol(symbol_from_text(symbol))),
41 Self::Path(path) => Some(KernelLibSourceSpec::Path(path.clone())),
42 Self::Url(url) => Some(KernelLibSourceSpec::Url(url.clone())),
43 Self::Bytes(bytes) => Some(KernelLibSourceSpec::Bytes(bytes.clone())),
44 Self::Host(_) | Self::CratesIo(_) => None,
45 }
46 }
47
48 pub(crate) fn from_kernel_data_source(source: KernelLibSourceSpec) -> Self {
49 match source {
50 KernelLibSourceSpec::Symbol(symbol) => Self::Symbol(symbol.to_string()),
51 KernelLibSourceSpec::Path(path) => Self::Path(path),
52 KernelLibSourceSpec::Url(url) => Self::Url(url),
53 KernelLibSourceSpec::Bytes(bytes) => Self::Bytes(bytes),
54 }
55 }
56}
57
58impl fmt::Display for LibSourceSpec {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 Self::Symbol(symbol) => write!(f, "symbol:{symbol}"),
62 Self::Path(path) => write!(f, "path:{}", path.display()),
63 Self::Url(url) => write!(f, "url:{url}"),
64 Self::Bytes(bytes) => write!(f, "bytes:{} bytes", bytes.len()),
65 Self::Host(name) => write!(f, "host:{name}"),
66 Self::CratesIo(spec) => write!(f, "crates.io:{spec}"),
67 }
68 }
69}
70
71pub(crate) fn symbol_from_text(text: &str) -> Symbol {
72 match text.split_once('/') {
73 Some((namespace, name)) if !namespace.is_empty() && !name.is_empty() => {
74 Symbol::qualified(namespace.to_owned(), name.to_owned())
75 }
76 _ => Symbol::new(text.to_owned()),
77 }
78}
79
80impl FromStr for LibSourceSpec {
81 type Err = CliError;
82
83 fn from_str(source: &str) -> Result<Self, Self::Err> {
84 let Some((kind, rest)) = source.split_once(':') else {
85 return Err(CliError::new("library source must use kind:value syntax"));
86 };
87 if rest.is_empty() {
88 return Err(CliError::new(format!("{kind}: source value is empty")));
89 }
90 match kind {
91 "symbol" => Ok(Self::Symbol(rest.to_owned())),
92 "path" => Ok(Self::Path(PathBuf::from(rest))),
93 "url" => Ok(Self::Url(rest.to_owned())),
94 "bytes" => Ok(Self::Bytes(rest.as_bytes().to_vec())),
95 "host" => Ok(Self::Host(rest.to_owned())),
96 "crates.io" => Ok(Self::CratesIo(rest.parse::<CratesIoSpec>()?)),
97 _ => Err(CliError::new(format!(
98 "unsupported library source kind: {kind}"
99 ))),
100 }
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn parses_supported_source_specs() {
110 assert_eq!(
111 "symbol:codec/lisp".parse::<LibSourceSpec>().unwrap(),
112 LibSourceSpec::Symbol("codec/lisp".to_owned())
113 );
114 assert_eq!(
115 "path:./libs/demo.wasm".parse::<LibSourceSpec>().unwrap(),
116 LibSourceSpec::Path(PathBuf::from("./libs/demo.wasm"))
117 );
118 assert_eq!(
119 "url:https://example.invalid/demo.wasm"
120 .parse::<LibSourceSpec>()
121 .unwrap(),
122 LibSourceSpec::Url("https://example.invalid/demo.wasm".to_owned())
123 );
124 assert_eq!(
125 "bytes:abc".parse::<LibSourceSpec>().unwrap(),
126 LibSourceSpec::Bytes(b"abc".to_vec())
127 );
128 assert_eq!(
129 "host:test/demo".parse::<LibSourceSpec>().unwrap(),
130 LibSourceSpec::Host("test/demo".to_owned())
131 );
132 assert_eq!(
133 "crates.io:sim-codec-lisp@0.1.0"
134 .parse::<LibSourceSpec>()
135 .unwrap(),
136 LibSourceSpec::CratesIo("sim-codec-lisp@0.1.0".parse().unwrap())
137 );
138 }
139
140 #[test]
141 fn source_parse_errors_are_typed() {
142 assert_eq!(
143 "codec/lisp"
144 .parse::<LibSourceSpec>()
145 .unwrap_err()
146 .to_string(),
147 "library source must use kind:value syntax"
148 );
149 assert_eq!(
150 "symbol:".parse::<LibSourceSpec>().unwrap_err().to_string(),
151 "symbol: source value is empty"
152 );
153 assert_eq!(
154 "crate:sim"
155 .parse::<LibSourceSpec>()
156 .unwrap_err()
157 .to_string(),
158 "unsupported library source kind: crate"
159 );
160 }
161}