1use std::{error::Error, fmt};
2
3use wasm_bindgen::prelude::*;
4use wowlab_types::wasm::{WasmCommonError, decode_hex_to_bytes};
5
6#[derive(Debug)]
8pub struct WasmAnalyticsError {
9 kind: WasmAnalyticsErrorKind,
10}
11
12#[derive(Debug)]
13enum WasmAnalyticsErrorKind {
14 Input(WasmCommonError),
15 Analytics(wowlab_analytics::AnalyticsDecodeError),
16}
17
18impl fmt::Display for WasmAnalyticsError {
19 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20 match &self.kind {
21 WasmAnalyticsErrorKind::Input(error) => fmt::Display::fmt(error, f),
22 WasmAnalyticsErrorKind::Analytics(error) => fmt::Display::fmt(error, f),
23 }
24 }
25}
26
27impl From<WasmCommonError> for WasmAnalyticsError {
28 fn from(error: WasmCommonError) -> Self {
29 Self {
30 kind: WasmAnalyticsErrorKind::Input(error),
31 }
32 }
33}
34
35impl From<wowlab_analytics::AnalyticsDecodeError> for WasmAnalyticsError {
36 fn from(error: wowlab_analytics::AnalyticsDecodeError) -> Self {
37 Self {
38 kind: WasmAnalyticsErrorKind::Analytics(error),
39 }
40 }
41}
42
43impl Error for WasmAnalyticsError {
44 fn source(&self) -> Option<&(dyn Error + 'static)> {
45 match &self.kind {
46 WasmAnalyticsErrorKind::Input(_) => None,
47 WasmAnalyticsErrorKind::Analytics(error) => Some(error),
48 }
49 }
50}
51
52impl From<WasmAnalyticsError> for JsValue {
53 fn from(error: WasmAnalyticsError) -> Self {
54 let js_error = js_sys::Error::new(&error.to_string());
55
56 js_error.set_name("AnalyticsDecodeError");
57
58 js_error.into()
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use std::error::Error as _;
65
66 use googletest::prelude::*;
67
68 use super::*;
69
70 #[gtest]
71 fn analytics_error_retains_the_prost_source_until_javascript_conversion() -> Result<()> {
72 let analytics_error = wowlab_analytics::decode_and_derive(&[0x80], &[])
73 .err()
74 .or_fail()?;
75 let error = WasmAnalyticsError::from(analytics_error);
76
77 let analytics_source = error.source().or_fail()?;
78 let analytics_kind = analytics_source.source().or_fail()?;
79 let prost_source = analytics_kind.source().or_fail()?;
80
81 verify_that!(
82 prost_source.to_string(),
83 eq("failed to decode Protobuf message: invalid varint")
84 )
85 }
86}
87
88#[wasm_bindgen(js_name = "computeChartOverlay")]
89#[must_use]
90pub fn wasm_compute_chart_overlay(y_values: &[f64]) -> wowlab_types::stats::ChartOverlayView {
91 wowlab_types::stats::compute_chart_overlay(y_values)
92}
93
94#[wasm_bindgen(js_name = "computeScatterOverlay")]
95#[must_use]
96pub fn wasm_compute_scatter_overlay(
97 xs: &[f64],
98 ys: &[f64],
99) -> wowlab_types::stats::ScatterOverlayView {
100 wowlab_types::stats::compute_scatter_overlay(xs, ys)
101}
102
103#[wasm_bindgen(js_name = "computeTimelineMetrics")]
104#[must_use]
105pub fn wasm_compute_timeline_metrics(
106 geometry: &wowlab_types::stats::TimelineGeometry,
107) -> wowlab_types::stats::TimelineMetrics {
108 wowlab_types::stats::compute_timeline_metrics(geometry)
109}
110
111#[wasm_bindgen(js_name = "zoomTimelineAt")]
113#[must_use]
114pub fn wasm_zoom_timeline_at(
115 geometry: &wowlab_types::stats::TimelineGeometry,
116 local_x: f64,
117 zoom_in: bool,
118 zoom_factor: f64,
119) -> wowlab_types::stats::TimelineViewport {
120 wowlab_types::stats::zoom_timeline_at(geometry, local_x, zoom_in, zoom_factor)
121}
122
123#[wasm_bindgen(js_name = "panTimelineBy")]
124#[must_use]
125pub fn wasm_pan_timeline_by(
126 geometry: &wowlab_types::stats::TimelineGeometry,
127 start_pan_ms: f64,
128 delta_px: f64,
129) -> wowlab_types::stats::TimelineViewport {
130 wowlab_types::stats::pan_timeline_by(geometry, start_pan_ms, delta_px)
131}
132
133#[wasm_bindgen(js_name = decodeAndDerive)]
135pub fn wasm_decode_and_derive(
136 result_hex: &str,
137 timeline_hex: &str,
138) -> Result<wowlab_analytics::AnalyticsView, WasmAnalyticsError> {
139 let result_bytes = decode_hex_to_bytes(result_hex)?;
140 let timeline_bytes = decode_hex_to_bytes(timeline_hex)?;
141
142 Ok(wowlab_analytics::decode_and_derive(
143 &result_bytes,
144 &timeline_bytes,
145 )?)
146}
147
148#[wasm_bindgen(js_name = decodeJobResult)]
150pub fn wasm_decode_job_result(
151 result_hex: &str,
152 timeline_hex: &str,
153) -> Result<wowlab_analytics::JobResultView, WasmAnalyticsError> {
154 let result_bytes = decode_hex_to_bytes(result_hex)?;
155 let timeline_bytes = decode_hex_to_bytes(timeline_hex)?;
156
157 Ok(wowlab_analytics::decode_job_result(
158 &result_bytes,
159 &timeline_bytes,
160 )?)
161}