use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct JsBriefcaseClient {
client_id: String,
permissions: Vec<String>,
rate_limit_rps: Option<u32>,
}
#[wasm_bindgen]
impl JsBriefcaseClient {
#[wasm_bindgen(constructor)]
pub fn new(client_id: String, permissions_json: &str) -> Result<JsBriefcaseClient, JsError> {
let permissions: Vec<String> = serde_json::from_str(permissions_json)
.map_err(|e| JsError::new(&format!("Invalid permissions JSON: {}", e)))?;
Ok(JsBriefcaseClient {
client_id,
permissions,
rate_limit_rps: None,
})
}
#[wasm_bindgen(js_name = "fromValidationResponse")]
pub fn from_validation_response(json: &str) -> Result<JsBriefcaseClient, JsError> {
let parsed: serde_json::Value = serde_json::from_str(json)
.map_err(|e| JsError::new(&format!("Invalid JSON: {}", e)))?;
let valid = parsed["valid"].as_bool().unwrap_or(false);
if !valid {
return Err(JsError::new("Validation response indicates invalid key"));
}
let client = &parsed["client"];
let client_id = client["client_id"]
.as_str()
.ok_or_else(|| JsError::new("Missing client.client_id"))?
.to_string();
let permissions = client["permissions"]
.as_array()
.ok_or_else(|| JsError::new("Missing client.permissions"))?
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect();
let rate_limit_rps = client["rate_limit_rps"].as_u64().map(|v| v as u32);
Ok(JsBriefcaseClient {
client_id,
permissions,
rate_limit_rps,
})
}
#[wasm_bindgen(js_name = "clientId")]
pub fn client_id(&self) -> String {
self.client_id.clone()
}
#[wasm_bindgen(js_name = "hasPermission")]
pub fn has_permission(&self, permission: &str) -> bool {
self.permissions.iter().any(|p| p == permission)
}
#[wasm_bindgen(js_name = "getPermissions")]
pub fn get_permissions(&self) -> Vec<String> {
self.permissions.clone()
}
#[wasm_bindgen(js_name = "rateLimitRps")]
pub fn rate_limit_rps(&self) -> Option<u32> {
self.rate_limit_rps
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constructor_valid() {
let client = JsBriefcaseClient::new("acme".into(), r#"["read","write"]"#).unwrap();
assert_eq!(client.client_id(), "acme");
assert!(client.has_permission("read"));
assert!(client.has_permission("write"));
assert!(!client.has_permission("delete"));
}
#[test]
fn test_constructor_empty_permissions() {
let client = JsBriefcaseClient::new("acme".into(), "[]").unwrap();
assert_eq!(client.client_id(), "acme");
assert!(!client.has_permission("read"));
assert_eq!(client.get_permissions().len(), 0);
}
#[test]
#[cfg(target_arch = "wasm32")]
fn test_constructor_invalid_json() {
let result = JsBriefcaseClient::new("acme".into(), "not json");
assert!(result.is_err());
}
#[test]
fn test_from_validation_response_valid() {
let json = r#"{
"valid": true,
"client": {
"client_id": "beta",
"permissions": ["read", "write", "replay"],
"rate_limit_rps": 200
},
"expires_at": "2026-02-07T00:00:00Z"
}"#;
let client = JsBriefcaseClient::from_validation_response(json).unwrap();
assert_eq!(client.client_id(), "beta");
assert!(client.has_permission("read"));
assert!(client.has_permission("replay"));
assert!(!client.has_permission("admin"));
assert_eq!(client.rate_limit_rps(), Some(200));
assert_eq!(client.get_permissions().len(), 3);
}
#[test]
#[cfg(target_arch = "wasm32")]
fn test_from_validation_response_invalid_key() {
let json = r#"{
"valid": false,
"error": "Invalid API key"
}"#;
let result = JsBriefcaseClient::from_validation_response(json);
assert!(result.is_err());
}
#[test]
#[cfg(target_arch = "wasm32")]
fn test_from_validation_response_missing_client() {
let json = r#"{"valid": true}"#;
let result = JsBriefcaseClient::from_validation_response(json);
assert!(result.is_err());
}
#[test]
#[cfg(target_arch = "wasm32")]
fn test_from_validation_response_invalid_json() {
let result = JsBriefcaseClient::from_validation_response("{invalid");
assert!(result.is_err());
}
#[test]
fn test_from_validation_response_no_rate_limit() {
let json = r#"{
"valid": true,
"client": {
"client_id": "basic",
"permissions": ["read"]
},
"expires_at": "2026-02-07T00:00:00Z"
}"#;
let client = JsBriefcaseClient::from_validation_response(json).unwrap();
assert_eq!(client.rate_limit_rps(), None);
}
#[test]
fn test_has_permission_case_sensitive() {
let client = JsBriefcaseClient::new("acme".into(), r#"["Read"]"#).unwrap();
assert!(client.has_permission("Read"));
assert!(!client.has_permission("read"));
}
}