use briefcase_core::{DecisionSnapshot, Input, ModelParameters, Output, Snapshot, SnapshotType};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct WasmInput {
inner: Input,
}
#[wasm_bindgen]
impl WasmInput {
#[wasm_bindgen(constructor)]
pub fn new(name: &str, value: &JsValue, data_type: &str) -> Result<WasmInput, JsValue> {
let json_value: serde_json::Value = serde_wasm_bindgen::from_value(value.clone())
.map_err(|e| JsValue::from_str(&format!("Failed to parse value: {}", e)))?;
Ok(WasmInput {
inner: Input::new(name.to_string(), json_value, data_type.to_string()),
})
}
#[wasm_bindgen(getter)]
pub fn name(&self) -> String {
self.inner.name.clone()
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> Result<JsValue, JsValue> {
serde_wasm_bindgen::to_value(&self.inner.value)
.map_err(|e| JsValue::from_str(&format!("Conversion error: {}", e)))
}
#[wasm_bindgen(getter)]
pub fn data_type(&self) -> String {
self.inner.data_type.clone()
}
#[wasm_bindgen(js_name = toObject)]
pub fn to_object(&self) -> Result<JsValue, JsValue> {
let obj = serde_json::json!({
"name": self.inner.name,
"value": self.inner.value,
"data_type": self.inner.data_type,
});
serde_wasm_bindgen::to_value(&obj)
.map_err(|e| JsValue::from_str(&format!("Conversion error: {}", e)))
}
}
#[wasm_bindgen]
pub struct WasmOutput {
inner: Output,
}
#[wasm_bindgen]
impl WasmOutput {
#[wasm_bindgen(constructor)]
pub fn new(name: &str, value: &JsValue, data_type: &str) -> Result<WasmOutput, JsValue> {
let json_value: serde_json::Value = serde_wasm_bindgen::from_value(value.clone())
.map_err(|e| JsValue::from_str(&format!("Failed to parse value: {}", e)))?;
Ok(WasmOutput {
inner: Output::new(name.to_string(), json_value, data_type.to_string()),
})
}
#[wasm_bindgen(js_name = withConfidence)]
pub fn with_confidence(&mut self, confidence: f64) {
self.inner = self.inner.clone().with_confidence(confidence);
}
#[wasm_bindgen(getter)]
pub fn name(&self) -> String {
self.inner.name.clone()
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> Result<JsValue, JsValue> {
serde_wasm_bindgen::to_value(&self.inner.value)
.map_err(|e| JsValue::from_str(&format!("Conversion error: {}", e)))
}
#[wasm_bindgen(getter)]
pub fn data_type(&self) -> String {
self.inner.data_type.clone()
}
#[wasm_bindgen(getter)]
pub fn confidence(&self) -> Option<f64> {
self.inner.confidence
}
#[wasm_bindgen(js_name = toObject)]
pub fn to_object(&self) -> Result<JsValue, JsValue> {
let mut obj = serde_json::json!({
"name": self.inner.name,
"value": self.inner.value,
"data_type": self.inner.data_type,
});
if let Some(confidence) = self.inner.confidence {
obj["confidence"] =
serde_json::Value::Number(serde_json::Number::from_f64(confidence).unwrap());
}
serde_wasm_bindgen::to_value(&obj)
.map_err(|e| JsValue::from_str(&format!("Conversion error: {}", e)))
}
}
#[wasm_bindgen]
pub struct WasmModelParameters {
inner: ModelParameters,
}
#[wasm_bindgen]
impl WasmModelParameters {
#[wasm_bindgen(constructor)]
pub fn new(model_name: &str) -> WasmModelParameters {
WasmModelParameters {
inner: ModelParameters::new(model_name.to_string()),
}
}
#[wasm_bindgen(js_name = withProvider)]
pub fn with_provider(&mut self, provider: &str) {
self.inner = self.inner.clone().with_provider(provider.to_string());
}
#[wasm_bindgen(js_name = withParameter)]
pub fn with_parameter(&mut self, key: &str, value: &JsValue) -> Result<(), JsValue> {
let json_value: serde_json::Value = serde_wasm_bindgen::from_value(value.clone())
.map_err(|e| JsValue::from_str(&format!("Failed to parse parameter value: {}", e)))?;
self.inner = self
.inner
.clone()
.with_parameter(key.to_string(), json_value);
Ok(())
}
#[wasm_bindgen(getter)]
pub fn model_name(&self) -> String {
self.inner.model_name.clone()
}
#[wasm_bindgen(getter)]
pub fn provider(&self) -> Option<String> {
self.inner.provider.clone()
}
#[wasm_bindgen(getter)]
pub fn parameters(&self) -> Result<JsValue, JsValue> {
serde_wasm_bindgen::to_value(&self.inner.parameters)
.map_err(|e| JsValue::from_str(&format!("Conversion error: {}", e)))
}
}
#[wasm_bindgen]
pub struct WasmDecisionSnapshot {
pub(crate) inner: DecisionSnapshot,
}
#[wasm_bindgen]
impl WasmDecisionSnapshot {
#[wasm_bindgen(constructor)]
pub fn new(function_name: &str) -> WasmDecisionSnapshot {
WasmDecisionSnapshot {
inner: DecisionSnapshot::new(function_name.to_string()),
}
}
#[wasm_bindgen(js_name = withModule)]
pub fn with_module(&mut self, module_name: &str) {
self.inner = self.inner.clone().with_module(module_name.to_string());
}
#[wasm_bindgen(js_name = addInput)]
pub fn add_input(&mut self, input: &WasmInput) {
self.inner = self.inner.clone().add_input(input.inner.clone());
}
#[wasm_bindgen(js_name = addOutput)]
pub fn add_output(&mut self, output: &WasmOutput) {
self.inner = self.inner.clone().add_output(output.inner.clone());
}
#[wasm_bindgen(js_name = withModelParameters)]
pub fn with_model_parameters(&mut self, params: &WasmModelParameters) {
self.inner = self
.inner
.clone()
.with_model_parameters(params.inner.clone());
}
#[wasm_bindgen(js_name = withExecutionTime)]
pub fn with_execution_time(&mut self, time_ms: f64) {
self.inner = self.inner.clone().with_execution_time(time_ms);
}
#[wasm_bindgen(js_name = addTag)]
pub fn add_tag(&mut self, key: &str, value: &str) {
self.inner = self
.inner
.clone()
.add_tag(key.to_string(), value.to_string());
}
#[wasm_bindgen(getter)]
pub fn function_name(&self) -> String {
self.inner.function_name.clone()
}
#[wasm_bindgen(getter)]
pub fn module_name(&self) -> Option<String> {
self.inner.module_name.clone()
}
#[wasm_bindgen(getter)]
pub fn execution_time_ms(&self) -> Option<f64> {
self.inner.execution_time_ms
}
#[wasm_bindgen(getter)]
pub fn tags(&self) -> Result<JsValue, JsValue> {
serde_wasm_bindgen::to_value(&self.inner.tags)
.map_err(|e| JsValue::from_str(&format!("Conversion error: {}", e)))
}
#[wasm_bindgen(js_name = toObject)]
pub fn to_object(&self) -> Result<JsValue, JsValue> {
serde_wasm_bindgen::to_value(&self.inner)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
}
}
#[wasm_bindgen]
pub struct WasmSnapshot {
pub(crate) inner: Snapshot,
}
#[wasm_bindgen]
impl WasmSnapshot {
#[wasm_bindgen(constructor)]
pub fn new(snapshot_type: &str) -> Result<WasmSnapshot, JsValue> {
let snap_type = match snapshot_type {
"session" => SnapshotType::Session,
"decision" => SnapshotType::Decision,
"batch" => SnapshotType::Batch,
_ => {
return Err(JsValue::from_str(&format!(
"Invalid snapshot type: {}",
snapshot_type
)))
}
};
Ok(WasmSnapshot {
inner: Snapshot::new(snap_type),
})
}
#[wasm_bindgen(js_name = addDecision)]
pub fn add_decision(&mut self, decision: &WasmDecisionSnapshot) {
self.inner.add_decision(decision.inner.clone());
}
#[wasm_bindgen(getter)]
pub fn snapshot_type(&self) -> String {
format!("{:?}", self.inner.snapshot_type).to_lowercase()
}
#[wasm_bindgen(getter)]
pub fn decision_count(&self) -> u32 {
self.inner.decisions.len() as u32
}
#[wasm_bindgen(js_name = toObject)]
pub fn to_object(&self) -> Result<JsValue, JsValue> {
serde_wasm_bindgen::to_value(&self.inner)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))
}
}
#[cfg(test)]
mod tests {
use briefcase_core::{
DecisionSnapshot, Input, ModelParameters, Output, Snapshot, SnapshotType,
};
#[test]
fn test_input_construction() {
let input = Input::new("query", serde_json::json!("hello"), "string");
assert_eq!(input.name, "query");
assert_eq!(input.value, serde_json::json!("hello"));
assert_eq!(input.data_type, "string");
}
#[test]
fn test_output_construction() {
let output = Output::new("result", serde_json::json!(42), "number");
assert_eq!(output.name, "result");
assert_eq!(output.value, serde_json::json!(42));
assert_eq!(output.data_type, "number");
assert!(output.confidence.is_none());
}
#[test]
fn test_output_with_confidence() {
let output =
Output::new("result", serde_json::json!("yes"), "string").with_confidence(0.95);
assert_eq!(output.confidence, Some(0.95));
}
#[test]
fn test_model_parameters_construction() {
let params = ModelParameters::new("gpt-4")
.with_provider("openai")
.with_parameter("temperature", serde_json::json!(0.7));
assert_eq!(params.model_name, "gpt-4");
assert_eq!(params.provider, Some("openai".to_string()));
assert_eq!(
params.parameters.get("temperature"),
Some(&serde_json::json!(0.7))
);
}
#[test]
fn test_decision_snapshot_builder() {
let decision = DecisionSnapshot::new("classify")
.with_module("nlp")
.add_input(Input::new("text", serde_json::json!("hello"), "string"))
.add_output(Output::new(
"label",
serde_json::json!("greeting"),
"string",
))
.with_execution_time(42.5)
.add_tag("env", "test");
assert_eq!(decision.function_name, "classify");
assert_eq!(decision.module_name, Some("nlp".to_string()));
assert_eq!(decision.inputs.len(), 1);
assert_eq!(decision.outputs.len(), 1);
assert_eq!(decision.execution_time_ms, Some(42.5));
assert_eq!(decision.tags.get("env"), Some(&"test".to_string()));
}
#[test]
fn test_decision_snapshot_minimal() {
let decision = DecisionSnapshot::new("simple");
assert_eq!(decision.function_name, "simple");
assert!(decision.module_name.is_none());
assert!(decision.inputs.is_empty());
assert!(decision.outputs.is_empty());
assert!(decision.execution_time_ms.is_none());
assert!(decision.tags.is_empty());
assert!(decision.model_parameters.is_none());
}
#[test]
fn test_decision_snapshot_multiple_inputs_outputs() {
let decision = DecisionSnapshot::new("multi")
.add_input(Input::new("a", serde_json::json!(1), "int"))
.add_input(Input::new("b", serde_json::json!(2), "int"))
.add_input(Input::new("c", serde_json::json!(3), "int"))
.add_output(Output::new("sum", serde_json::json!(6), "int"))
.add_output(Output::new("avg", serde_json::json!(2.0), "float"));
assert_eq!(decision.inputs.len(), 3);
assert_eq!(decision.outputs.len(), 2);
}
#[test]
fn test_snapshot_session_type() {
let snapshot = Snapshot::new(SnapshotType::Session);
assert!(matches!(snapshot.snapshot_type, SnapshotType::Session));
assert!(snapshot.decisions.is_empty());
}
#[test]
fn test_snapshot_decision_type() {
let snapshot = Snapshot::new(SnapshotType::Decision);
assert!(matches!(snapshot.snapshot_type, SnapshotType::Decision));
}
#[test]
fn test_snapshot_batch_type() {
let snapshot = Snapshot::new(SnapshotType::Batch);
assert!(matches!(snapshot.snapshot_type, SnapshotType::Batch));
}
#[test]
fn test_snapshot_add_decisions() {
let mut snapshot = Snapshot::new(SnapshotType::Session);
let d1 = DecisionSnapshot::new("fn1");
let d2 = DecisionSnapshot::new("fn2");
snapshot.add_decision(d1);
snapshot.add_decision(d2);
assert_eq!(snapshot.decisions.len(), 2);
}
#[test]
fn test_decision_serialization_roundtrip() {
let decision = DecisionSnapshot::new("roundtrip")
.with_module("test")
.add_input(Input::new(
"x",
serde_json::json!({"nested": true}),
"object",
))
.add_output(
Output::new("y", serde_json::json!([1, 2, 3]), "array").with_confidence(0.8),
)
.with_model_parameters(ModelParameters::new("gpt-4").with_provider("openai"))
.with_execution_time(99.9)
.add_tag("version", "2.1.19");
let json_str = serde_json::to_string(&decision).unwrap();
let deserialized: DecisionSnapshot = serde_json::from_str(&json_str).unwrap();
assert_eq!(deserialized.function_name, "roundtrip");
assert_eq!(deserialized.module_name, Some("test".to_string()));
assert_eq!(deserialized.inputs.len(), 1);
assert_eq!(deserialized.outputs.len(), 1);
assert_eq!(deserialized.outputs[0].confidence, Some(0.8));
assert_eq!(deserialized.execution_time_ms, Some(99.9));
assert_eq!(
deserialized.tags.get("version"),
Some(&"2.1.19".to_string())
);
}
#[test]
fn test_input_complex_json_value() {
let value = serde_json::json!({
"embeddings": [[0.1, 0.2], [0.3, 0.4]],
"metadata": {"source": "api", "count": 42}
});
let input = Input::new("embedding_data", value.clone(), "object");
assert_eq!(input.value, value);
}
#[test]
fn test_model_parameters_multiple_params() {
let params = ModelParameters::new("claude-3")
.with_provider("anthropic")
.with_parameter("temperature", serde_json::json!(0.7))
.with_parameter("max_tokens", serde_json::json!(1024))
.with_parameter("top_p", serde_json::json!(0.9));
assert_eq!(params.parameters.len(), 3);
assert_eq!(
params.parameters.get("max_tokens"),
Some(&serde_json::json!(1024))
);
}
}