Skip to main content

wowlab_wasm/
sim_config.rs

1use std::{error::Error, fmt};
2
3use wasm_bindgen::prelude::*;
4use wowlab_common::sim::{
5    intent::IntentConfigError, job_meta::JobMetaError, sentinel_config::SentinelConfigError,
6};
7use wowlab_types::{
8    game::ImplementedSpecInfo,
9    wasm::{WasmCommonError, from_js},
10};
11
12/// Simulation-configuration failure converted to a named JavaScript error at the export boundary.
13#[derive(Debug)]
14#[non_exhaustive]
15pub struct WasmSimConfigError {
16    kind: WasmSimConfigErrorKind,
17}
18
19#[derive(Debug)]
20enum WasmSimConfigErrorKind {
21    Input(WasmCommonError),
22    Intent(IntentConfigError),
23    Sentinel(SentinelConfigError),
24    JobMeta(JobMetaError),
25}
26
27impl fmt::Display for WasmSimConfigError {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match &self.kind {
30            WasmSimConfigErrorKind::Input(error) => fmt::Display::fmt(error, f),
31            WasmSimConfigErrorKind::Intent(error) => fmt::Display::fmt(error, f),
32            WasmSimConfigErrorKind::Sentinel(error) => fmt::Display::fmt(error, f),
33            WasmSimConfigErrorKind::JobMeta(error) => {
34                write!(f, "parse job metadata: {error}")
35            }
36        }
37    }
38}
39
40impl Error for WasmSimConfigError {
41    fn source(&self) -> Option<&(dyn Error + 'static)> {
42        match &self.kind {
43            WasmSimConfigErrorKind::Input(_) => None,
44            WasmSimConfigErrorKind::Intent(error) => Some(error),
45            WasmSimConfigErrorKind::Sentinel(error) => Some(error),
46            WasmSimConfigErrorKind::JobMeta(error) => Some(error),
47        }
48    }
49}
50
51impl From<WasmCommonError> for WasmSimConfigError {
52    fn from(error: WasmCommonError) -> Self {
53        Self {
54            kind: WasmSimConfigErrorKind::Input(error),
55        }
56    }
57}
58
59impl From<IntentConfigError> for WasmSimConfigError {
60    fn from(error: IntentConfigError) -> Self {
61        Self {
62            kind: WasmSimConfigErrorKind::Intent(error),
63        }
64    }
65}
66
67impl From<SentinelConfigError> for WasmSimConfigError {
68    fn from(error: SentinelConfigError) -> Self {
69        Self {
70            kind: WasmSimConfigErrorKind::Sentinel(error),
71        }
72    }
73}
74
75impl From<JobMetaError> for WasmSimConfigError {
76    fn from(error: JobMetaError) -> Self {
77        Self {
78            kind: WasmSimConfigErrorKind::JobMeta(error),
79        }
80    }
81}
82
83impl From<WasmSimConfigError> for JsValue {
84    fn from(error: WasmSimConfigError) -> Self {
85        let name = match &error.kind {
86            WasmSimConfigErrorKind::Input(_) => "DecodeError",
87            WasmSimConfigErrorKind::Intent(_) | WasmSimConfigErrorKind::Sentinel(_) => {
88                "ConfigError"
89            }
90            WasmSimConfigErrorKind::JobMeta(_) => "ParseError",
91        };
92        let js_error = js_sys::Error::new(&error.to_string());
93
94        js_error.set_name(name);
95
96        js_error.into()
97    }
98}
99
100/// Build a TOML sim config string from a JS object.
101#[wasm_bindgen(js_name = buildSimConfig)]
102pub fn wasm_build_sim_config(input: JsValue) -> Result<String, WasmSimConfigError> {
103    let input: wowlab_common::sim::intent::IntentInput = from_js(input)?;
104
105    Ok(wowlab_common::sim::intent::build_sim_config(input)?)
106}
107
108/// Build a TOML sentinel config string from a JS object.
109#[wasm_bindgen(js_name = buildSentinelConfig)]
110pub fn wasm_build_sentinel_config(input: JsValue) -> Result<String, WasmSimConfigError> {
111    let config: wowlab_common::sim::sentinel_config::SentinelConfig = from_js(input)?;
112
113    Ok(wowlab_common::sim::sentinel_config::build_sentinel_config(
114        &config,
115    )?)
116}
117
118/// Parse and validate engine `getImplementedSpecs()` payload.
119#[wasm_bindgen(js_name = parseImplementedSpecs)]
120pub fn wasm_parse_implemented_specs(
121    input: JsValue,
122) -> Result<Vec<ImplementedSpecInfo>, WasmCommonError> {
123    from_js(input)
124}
125
126#[wasm_bindgen(js_name = parseJobMeta)]
127pub fn wasm_parse_job_meta(
128    json: &str,
129) -> Result<wowlab_common::sim::job_meta::JobMeta, WasmSimConfigError> {
130    Ok(wowlab_common::sim::job_meta::parse_job_meta(json)?)
131}
132
133#[cfg(test)]
134mod tests {
135    use std::error::Error as _;
136
137    use googletest::prelude::*;
138
139    use super::*;
140
141    #[gtest]
142    fn intent_error_retains_common_and_toml_sources_until_javascript_conversion() -> Result<()> {
143        let common_error = wowlab_common::sim::intent::parse_sim_config("[")
144            .err()
145            .or_fail()?;
146        let error = WasmSimConfigError::from(common_error);
147
148        let common_source = error.source().or_fail()?;
149
150        verify_true!(common_source.is::<IntentConfigError>())?;
151
152        verify_that!(common_source.source(), some(anything()))
153    }
154
155    #[gtest]
156    fn sentinel_error_retains_common_and_toml_sources_until_javascript_conversion() -> Result<()> {
157        let common_error = wowlab_common::sim::sentinel_config::parse_sentinel_config("[")
158            .err()
159            .or_fail()?;
160        let error = WasmSimConfigError::from(common_error);
161
162        let common_source = error.source().or_fail()?;
163
164        verify_true!(common_source.is::<SentinelConfigError>())?;
165
166        verify_that!(common_source.source(), some(anything()))
167    }
168
169    #[gtest]
170    fn job_meta_error_retains_common_and_json_sources_until_javascript_conversion() -> Result<()> {
171        let error = wasm_parse_job_meta("{").err().or_fail()?;
172
173        let common_source = error.source().or_fail()?;
174
175        verify_true!(common_source.is::<JobMetaError>())?;
176        verify_that!(common_source.source(), some(anything()))?;
177
178        verify_that!(
179            error.to_string(),
180            starts_with("parse job metadata: JSON parse error:")
181        )
182    }
183}