use std::str::FromStr;
use serde::de::DeserializeOwned;
use crate::error::Error;
use crate::subscribe::Subscription;
#[cfg(target_arch = "wasm32")]
mod wasm;
#[derive(Debug, Clone)]
pub struct Bridge {
selector: String,
}
impl Bridge {
pub fn new(selector: impl Into<String>) -> Self {
Self {
selector: selector.into(),
}
}
pub fn selector(&self) -> &str {
&self.selector
}
pub fn attr(&self, name: &str) -> Option<String> {
attr_impl(&self.selector, name)
}
pub fn read<T>(&self, name: &str) -> Option<T>
where
T: FromStr,
{
self.attr(name).and_then(|raw| raw.parse::<T>().ok())
}
pub fn read_json<T>(&self, name: &str) -> Option<T>
where
T: DeserializeOwned,
{
self.attr(name)
.and_then(|raw| serde_json::from_str::<T>(&raw).ok())
}
pub fn watch<T, F>(&self, name: &str, handler: F) -> Result<Subscription, Error>
where
T: FromStr + 'static,
F: Fn(T) + 'static,
{
watch_impl(&self.selector, name, move |raw: String| {
raw.parse::<T>().ok().map(|value| handler(value));
})
}
pub fn watch_json<T, F>(&self, name: &str, handler: F) -> Result<Subscription, Error>
where
T: DeserializeOwned + 'static,
F: Fn(T) + 'static,
{
watch_impl(&self.selector, name, move |raw: String| {
match serde_json::from_str::<T>(&raw) {
Ok(value) => handler(value),
Err(error) => {
log_decode_failure(&error.to_string());
}
}
})
}
}
#[cfg(target_arch = "wasm32")]
fn attr_impl(selector: &str, name: &str) -> Option<String> {
wasm::read_attribute(selector, name)
}
#[cfg(not(target_arch = "wasm32"))]
fn attr_impl(_selector: &str, _name: &str) -> Option<String> {
None
}
#[cfg(target_arch = "wasm32")]
fn watch_impl<F>(selector: &str, name: &str, on_change: F) -> Result<Subscription, Error>
where
F: Fn(String) + 'static,
{
wasm::watch(selector, name, on_change).map(|inner| Subscription::from_inner(Box::new(inner)))
}
#[cfg(not(target_arch = "wasm32"))]
fn watch_impl<F>(_selector: &str, _name: &str, _on_change: F) -> Result<Subscription, Error>
where
F: Fn(String) + 'static,
{
Ok(Subscription::inert())
}
#[cfg(target_arch = "wasm32")]
fn log_decode_failure(message: &str) {
use wasm_bindgen::JsValue;
web_sys::console::error_1(&JsValue::from_str(&format!(
"wasm_liveview::bridge: decode failed: {message}"
)));
}
#[cfg(not(target_arch = "wasm32"))]
fn log_decode_failure(_message: &str) {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn selector_is_preserved() {
let bridge = Bridge::new("#my-bridge");
assert_eq!(bridge.selector(), "#my-bridge");
}
#[test]
fn non_wasm_attr_is_none() {
let bridge = Bridge::new("#my-bridge");
assert!(bridge.attr("data-anything").is_none());
assert!(bridge.read::<f32>("data-anything").is_none());
assert!(bridge
.read_json::<serde_json::Value>("data-anything")
.is_none());
}
#[test]
fn non_wasm_watch_stubs_to_ok() {
let bridge = Bridge::new("#my-bridge");
let sub = bridge
.watch::<f32, _>("data-remaining-seconds", |_| {})
.unwrap();
drop(sub);
}
#[test]
fn non_wasm_watch_json_stubs_to_ok() {
let bridge = Bridge::new("#my-bridge");
let sub = bridge
.watch_json::<serde_json::Value, _>("data-payload", |_| {})
.unwrap();
sub.forget();
}
}