#![allow(clippy::missing_errors_doc)]
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn init_panic_hook() {
#[cfg(feature = "wasm")]
console_error_panic_hook::set_once();
}
#[wasm_bindgen]
pub fn encode(json: &str) -> Result<String, JsError> {
let value: serde_json::Value =
serde_json::from_str(json).map_err(|e| JsError::new(&format!("Invalid JSON: {e}")))?;
Ok(crate::encode::encode(value, None))
}
#[wasm_bindgen]
pub fn encode_with_options(json: &str, options: JsValue) -> Result<String, JsError> {
let value: serde_json::Value =
serde_json::from_str(json).map_err(|e| JsError::new(&format!("Invalid JSON: {e}")))?;
let encode_options = parse_encode_options(options)?;
Ok(crate::encode::encode(value, encode_options))
}
#[wasm_bindgen]
pub fn decode(toon: &str) -> Result<String, JsError> {
let value = crate::decode::try_decode(toon, None)
.map_err(|e| JsError::new(&format!("Decode error: {e}")))?;
let serde_value: serde_json::Value = value.into();
Ok(serde_json::to_string(&serde_value).unwrap_or_default())
}
#[wasm_bindgen]
pub fn decode_with_options(toon: &str, options: JsValue) -> Result<String, JsError> {
let decode_options = parse_decode_options(options)?;
let value = crate::decode::try_decode(toon, decode_options)
.map_err(|e| JsError::new(&format!("Decode error: {e}")))?;
let serde_value: serde_json::Value = value.into();
Ok(serde_json::to_string(&serde_value).unwrap_or_default())
}
#[wasm_bindgen]
pub fn decode_pretty(toon: &str) -> Result<String, JsError> {
let value = crate::decode::try_decode(toon, None)
.map_err(|e| JsError::new(&format!("Decode error: {e}")))?;
let serde_value: serde_json::Value = value.into();
serde_json::to_string_pretty(&serde_value)
.map_err(|e| JsError::new(&format!("JSON stringify error: {e}")))
}
#[must_use]
#[wasm_bindgen]
pub fn version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::needless_pass_by_value
)]
fn parse_encode_options(
options: JsValue,
) -> Result<Option<crate::options::EncodeOptions>, JsError> {
use crate::options::{EncodeOptions, KeyFoldingMode};
if options.is_undefined() || options.is_null() {
return Ok(None);
}
let obj = js_sys::Object::try_from(&options)
.ok_or_else(|| JsError::new("Options must be an object"))?;
let indent = js_sys::Reflect::get(obj, &"indent".into())
.ok()
.and_then(|v| v.as_f64())
.map(|v| v as usize);
let delimiter = js_sys::Reflect::get(obj, &"delimiter".into())
.ok()
.and_then(|v| v.as_string())
.and_then(|s| s.chars().next());
let key_folding = js_sys::Reflect::get(obj, &"keyFolding".into())
.ok()
.and_then(|v| v.as_string())
.and_then(|s| match s.as_str() {
"off" => Some(KeyFoldingMode::Off),
"safe" => Some(KeyFoldingMode::Safe),
_ => None,
});
let flatten_depth = js_sys::Reflect::get(obj, &"flattenDepth".into())
.ok()
.and_then(|v| v.as_f64())
.map(|v| v as usize);
Ok(Some(EncodeOptions {
indent,
delimiter,
key_folding,
flatten_depth,
replacer: None,
}))
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::needless_pass_by_value
)]
fn parse_decode_options(
options: JsValue,
) -> Result<Option<crate::options::DecodeOptions>, JsError> {
use crate::options::{DecodeOptions, ExpandPathsMode};
if options.is_undefined() || options.is_null() {
return Ok(None);
}
let obj = js_sys::Object::try_from(&options)
.ok_or_else(|| JsError::new("Options must be an object"))?;
let indent = js_sys::Reflect::get(obj, &"indent".into())
.ok()
.and_then(|v| v.as_f64())
.map(|v| v as usize);
let strict = js_sys::Reflect::get(obj, &"strict".into())
.ok()
.and_then(|v| v.as_bool());
let expand_paths = js_sys::Reflect::get(obj, &"expandPaths".into())
.ok()
.and_then(|v| v.as_string())
.and_then(|s| match s.as_str() {
"off" => Some(ExpandPathsMode::Off),
"safe" => Some(ExpandPathsMode::Safe),
_ => None,
});
Ok(Some(DecodeOptions {
indent,
strict,
expand_paths,
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_simple() {
let result = encode(r#"{"name": "Alice"}"#).unwrap();
assert_eq!(result, "name: Alice");
}
#[test]
fn test_decode_simple() {
let result = decode("name: Alice").unwrap();
assert_eq!(result, r#"{"name":"Alice"}"#);
}
#[test]
fn test_roundtrip() {
let json = r#"{"users":[{"id":1.0,"name":"Alice"},{"id":2.0,"name":"Bob"}]}"#;
let toon = encode(json).unwrap();
let decoded = decode(&toon).unwrap();
let original: serde_json::Value = serde_json::from_str(json).unwrap();
let roundtrip: serde_json::Value = serde_json::from_str(&decoded).unwrap();
assert_eq!(original, roundtrip);
}
}