cedarling_wasm/lib.rs
1// This software is available under the Apache-2.0 license.
2// See https://www.apache.org/licenses/LICENSE-2.0.txt for full text.
3//
4// Copyright (c) 2024, Gluu, Inc.
5
6use cedarling::bindings::cedar_policy;
7use cedarling::{
8 AuthorizeMultiIssuerRequest, BatchAuthorizeMultiIssuerRequest,
9 BatchAuthorizeResponse as CedarBatchAuthorizeResponse, BatchAuthorizeUnsignedRequest,
10 BatchItemError as CedarBatchItemError, BootstrapConfig, BootstrapConfigRaw, DataApi,
11 DataEntry as CedarDataEntry, DataStoreStats as CedarDataStoreStats, LogStorage, PolicyId,
12 RequestUnsigned, TrustedIssuerLoadingInfo,
13};
14use serde::ser::{Serialize, SerializeStruct, Serializer};
15use serde_json::json;
16use serde_wasm_bindgen::Error;
17use std::rc::Rc;
18use std::time::Duration;
19use wasm_bindgen::prelude::*;
20use wasm_bindgen_futures::js_sys::{self, Array, Map, Object, Reflect};
21
22#[cfg(test)]
23mod tests;
24
25/// The instance of the Cedarling application.
26#[wasm_bindgen]
27#[derive(Clone)]
28pub struct Cedarling {
29 instance: cedarling::Cedarling,
30}
31
32/// A WASM wrapper for the Rust `cedarling::MultiIssuerAuthorizeResult` struct.
33/// Represents the result of a multi-issuer authorization request.
34#[wasm_bindgen]
35#[derive(Clone, Debug, serde::Serialize)]
36pub struct MultiIssuerAuthorizeResult {
37 /// Result of Cedar policy authorization
38 #[wasm_bindgen(getter_with_clone)]
39 pub response: AuthorizeResultResponse,
40
41 /// Result of authorization
42 /// true means `ALLOW`
43 /// false means `Deny`
44 pub decision: bool,
45
46 /// Request ID of the authorization request
47 #[wasm_bindgen(getter_with_clone)]
48 pub request_id: String,
49}
50
51#[wasm_bindgen]
52impl MultiIssuerAuthorizeResult {
53 /// Convert `MultiIssuerAuthorizeResult` to json string value
54 #[wasm_bindgen(js_name = jsonString)]
55 pub fn json_string(&self) -> String {
56 json!(self).to_string()
57 }
58}
59
60impl From<cedarling::MultiIssuerAuthorizeResult> for MultiIssuerAuthorizeResult {
61 fn from(value: cedarling::MultiIssuerAuthorizeResult) -> Self {
62 Self {
63 response: AuthorizeResultResponse {
64 inner: Rc::new(value.response),
65 },
66 decision: value.decision,
67 request_id: value.request_id,
68 }
69 }
70}
71
72/// Creates a Cedarling application from bootstrap properties.
73///
74/// # Arguments
75///
76/// * `config` - A JavaScript `Map` or plain object of Cedarling
77/// bootstrap-property names and values.
78///
79/// # Example
80///
81/// ```javascript
82/// await initWasm();
83/// const cedarling = await init({
84/// CEDARLING_APPLICATION_NAME: "task-api",
85/// CEDARLING_POLICY_STORE_URI: "https://example.com/policy-store.cjar",
86/// CEDARLING_LOG_TYPE: "memory",
87/// CEDARLING_LOG_TTL: 120,
88/// });
89/// ```
90#[wasm_bindgen]
91pub async fn init(config: JsValue) -> Result<Cedarling, Error> {
92 if config.is_instance_of::<Map>() {
93 // convert to map
94 let config_map: Map = config.unchecked_into();
95 Cedarling::new_from_map(config_map).await
96 } else if let Some(config_object) = Object::try_from(&config) {
97 Cedarling::new(config_object).await
98 } else {
99 Err(Error::new("config should be Map or Object"))
100 }
101}
102
103/// Create a new instance of the Cedarling application from archive bytes.
104///
105/// This function allows loading a policy store from a Cedar Archive (.cjar)
106/// that was fetched with custom logic (e.g., with authentication headers).
107///
108/// # Arguments
109/// * `config` - Bootstrap configuration (Map or Object) without a policy-store
110/// source property. The archive bytes provide the policy store.
111/// * `archive_bytes` - The .cjar archive bytes (Uint8Array)
112///
113/// # Example
114/// ```javascript
115/// await initWasm();
116/// const config = {
117/// CEDARLING_APPLICATION_NAME: "task-api",
118/// CEDARLING_LOG_TYPE: "memory",
119/// CEDARLING_LOG_TTL: 120,
120/// };
121/// const response = await fetch("https://example.com/policy-store.cjar", {
122/// headers: { Authorization: "Bearer <token>" },
123/// });
124/// if (!response.ok) throw new Error("Unable to fetch policy store");
125/// const archiveBytes = new Uint8Array(await response.arrayBuffer());
126/// const cedarling = await initFromArchiveBytes(config, archiveBytes);
127/// ```
128#[wasm_bindgen(js_name = initFromArchiveBytes)]
129pub async fn init_from_archive_bytes(
130 config: JsValue,
131 archive_bytes: js_sys::Uint8Array,
132) -> Result<Cedarling, Error> {
133 use cedarling::PolicyStoreSource;
134
135 // Convert Uint8Array to Vec<u8>
136 let bytes: Vec<u8> = archive_bytes.to_vec();
137
138 // Parse the config
139 let config_object = if config.is_instance_of::<Map>() {
140 let config_map: Map = config.unchecked_into();
141 Object::from_entries(&config_map.unchecked_into())?
142 } else if let Some(obj) = Object::try_from(&config) {
143 obj.clone()
144 } else {
145 return Err(Error::new("config should be Map or Object"));
146 };
147
148 let mut raw_config: BootstrapConfigRaw = serde_wasm_bindgen::from_value(config_object.into())?;
149
150 // Clear any existing policy store sources to avoid conflicts
151 // We'll set a dummy source temporarily to satisfy validation, then override with ArchiveBytes
152 raw_config.local_policy_store = None;
153 raw_config.policy_store_uri = None;
154 // Set a dummy .cjar file path to satisfy validation (will be overridden below)
155 raw_config.policy_store_local_fn = Some("dummy.cjar".to_string());
156
157 let mut bootstrap_config = BootstrapConfig::from_raw_config(&raw_config).map_err(Error::new)?;
158
159 // Override the policy store source with the archive bytes
160 bootstrap_config.policy_store_config.source = PolicyStoreSource::ArchiveBytes(bytes);
161
162 cedarling::Cedarling::new(&bootstrap_config)
163 .await
164 .map(|instance| Cedarling { instance })
165 .map_err(Error::new)
166}
167
168#[wasm_bindgen]
169impl Cedarling {
170 /// Creates a Cedarling application from bootstrap properties.
171 ///
172 /// # Arguments
173 ///
174 /// * `config` - A plain object of Cedarling bootstrap-property names and values.
175 ///
176 /// # Example
177 ///
178 /// ```javascript
179 /// await initWasm();
180 /// const cedarling = await Cedarling.new({
181 /// CEDARLING_APPLICATION_NAME: "task-api",
182 /// CEDARLING_POLICY_STORE_URI: "https://example.com/policy-store.cjar",
183 /// CEDARLING_LOG_TYPE: "memory",
184 /// CEDARLING_LOG_TTL: 120,
185 /// });
186 /// ```
187 pub async fn new(config: &Object) -> Result<Cedarling, Error> {
188 let config: BootstrapConfigRaw = serde_wasm_bindgen::from_value(config.into())?;
189
190 let config = BootstrapConfig::from_raw_config(&config).map_err(Error::new)?;
191
192 cedarling::Cedarling::new(&config)
193 .await
194 .map(|instance| Cedarling { instance })
195 .map_err(Error::new)
196 }
197
198 /// Creates a new Cedarling application from a JavaScript `Map`.
199 ///
200 /// # Arguments
201 ///
202 /// * `config` - A `Map` of Cedarling bootstrap-property names and values.
203 ///
204 /// # Example
205 ///
206 /// ```javascript
207 /// await initWasm();
208 /// const cedarling = await Cedarling.newFromMap(new Map([
209 /// ["CEDARLING_APPLICATION_NAME", "task-api"],
210 /// ["CEDARLING_POLICY_STORE_URI", "https://example.com/policy-store.cjar"],
211 /// ["CEDARLING_LOG_TYPE", "memory"],
212 /// ["CEDARLING_LOG_TTL", 120],
213 /// ]));
214 /// ```
215 #[wasm_bindgen(js_name = newFromMap)]
216 pub async fn new_from_map(config: Map) -> Result<Cedarling, Error> {
217 let conf_js_val = config.unchecked_into();
218
219 let conf_object = Object::from_entries(&conf_js_val)?;
220 Self::new(&conf_object).await
221 }
222
223 /// Authorize an unsigned request carrying an optional single principal.
224 /// Makes an authorization decision based on the [`RequestUnsigned`].
225 ///
226 /// When `principal` is omitted / `null` on the JS side the core uses Cedar
227 /// partial evaluation; residual-dependent requests fail closed with
228 /// `Decision::Deny` and surface residual policy ids in
229 /// `response.diagnostics.reason`.
230 ///
231 /// # Arguments
232 ///
233 /// * `request` - JSON string representation of [`RequestUnsigned`].
234 ///
235 /// # Example
236 ///
237 /// ```javascript
238 /// const result = await cedarling.authorizeUnsigned(JSON.stringify(request));
239 /// ```
240 #[wasm_bindgen(js_name = authorizeUnsigned)]
241 pub async fn authorize_unsigned(&self, request: &str) -> Result<AuthorizeResult, Error> {
242 let cedar_request: RequestUnsigned = serde_json::from_str(request)
243 .map_err(|e| Error::new(format!("invalid request JSON: {e}")))?;
244 let result = self
245 .instance
246 .authorize_unsigned(cedar_request)
247 .await
248 .map_err(Error::new)?;
249 Ok(result.into())
250 }
251
252 /// Authorize multi-issuer request.
253 /// Makes authorization decision based on multiple JWT tokens from different issuers.
254 ///
255 /// # Arguments
256 ///
257 /// * `request` - JSON string representation of [`AuthorizeMultiIssuerRequest`].
258 ///
259 /// # Example
260 ///
261 /// ```javascript
262 /// const result = await cedarling.authorizeMultiIssuer(JSON.stringify(request));
263 /// ```
264 #[wasm_bindgen(js_name = authorizeMultiIssuer)]
265 pub async fn authorize_multi_issuer(
266 &self,
267 request: &str,
268 ) -> Result<MultiIssuerAuthorizeResult, Error> {
269 let cedar_request: AuthorizeMultiIssuerRequest = serde_json::from_str(request)
270 .map_err(|e| Error::new(format!("invalid request JSON: {e}")))?;
271 let result = self
272 .instance
273 .authorize_multi_issuer(cedar_request)
274 .await
275 .map_err(Error::new)?;
276 Ok(result.into())
277 }
278
279 /// Authorize a batch of unsigned requests against one shared principal.
280 ///
281 /// Setup work (principal build + pushed-data snapshot) runs once and each
282 /// item is evaluated in input order. Results are returned inside a
283 /// [`BatchAuthorizeUnsignedResponse`] carrying the shared `batch_id`.
284 /// Batch-level failures (validation, principal parse) reject the whole
285 /// call; per-item failures are returned as `BatchItemError` results and
286 /// exposed by WASM with `is_ok=false` and `error`, while genuine Cedar
287 /// denials remain `AuthorizeResult` values with `decision=false`.
288 /// # Arguments
289 ///
290 /// * `request` - JSON string representation of [`BatchAuthorizeUnsignedRequest`].
291 ///
292 /// # Example
293 ///
294 /// ```javascript
295 /// const result = await cedarling.authorizeUnsignedBatch(JSON.stringify(batchRequest));
296 /// ```
297 #[wasm_bindgen(js_name = authorizeUnsignedBatch)]
298 pub async fn authorize_unsigned_batch(
299 &self,
300 request: &str,
301 ) -> Result<BatchAuthorizeUnsignedResponse, Error> {
302 let cedar_request: BatchAuthorizeUnsignedRequest = serde_json::from_str(request)
303 .map_err(|e| Error::new(format!("invalid request JSON: {e}")))?;
304 let response = self
305 .instance
306 .authorize_unsigned_batch(cedar_request)
307 .await
308 .map_err(Error::new)?;
309 Ok(response.into())
310 }
311
312 /// Authorize a batch of multi-issuer requests against one shared token set.
313 ///
314 /// Tokens are validated and token/issuer entities are built once, then
315 /// each item is evaluated in input order. Batch-level failures (validation,
316 /// JWT verification, status-list refresh) reject the whole call; per-item
317 /// failures are returned as `BatchItemError` results and exposed by WASM
318 /// with `is_ok=false` and `error`, while genuine Cedar denials remain
319 /// `AuthorizeResult` values with `decision=false`.
320 ///
321 /// # Arguments
322 ///
323 /// * `request` - JSON string representation of [`BatchAuthorizeMultiIssuerRequest`].
324 ///
325 /// # Example
326 ///
327 /// ```javascript
328 /// const result = await cedarling.authorizeMultiIssuerBatch(JSON.stringify(batchRequest));
329 /// ```
330 #[wasm_bindgen(js_name = authorizeMultiIssuerBatch)]
331 pub async fn authorize_multi_issuer_batch(
332 &self,
333 request: &str,
334 ) -> Result<BatchAuthorizeMultiIssuerResponse, Error> {
335 let cedar_request: BatchAuthorizeMultiIssuerRequest = serde_json::from_str(request)
336 .map_err(|e| Error::new(format!("invalid request JSON: {e}")))?;
337 let response = self
338 .instance
339 .authorize_multi_issuer_batch(cedar_request)
340 .await
341 .map_err(Error::new)?;
342 Ok(response.into())
343 }
344
345 /// Merge the annotations (`@key("value")`) of the given policies into a single object.
346 ///
347 /// Intended for resolving the determining policies of an authorization decision:
348 /// pass `result.response.diagnostics.reason`.
349 ///
350 /// Lossy: if the same annotation key appears on several policies, one value wins
351 /// arbitrarily. Use `annotationValues` / `annotationsByPolicy` when duplicates
352 /// matter. Unknown policy IDs are silently skipped.
353 ///
354 /// # Arguments
355 ///
356 /// * `policy_ids` - List of policy IDs whose annotations should be merged into
357 /// a single object. Typically `result.response.diagnostics.reason` from an
358 /// authorization result.
359 ///
360 /// # Example
361 ///
362 /// ```javascript
363 /// const annotations = cedarling.annotationsMap(result.response.diagnostics.reason);
364 /// // { redirect: "/upgrade", tier: "premium" }
365 /// ```
366 #[wasm_bindgen(js_name = annotationsMap)]
367 pub fn annotations_map(&self, policy_ids: Vec<String>) -> Result<JsValue, Error> {
368 let ids: Vec<PolicyId> = policy_ids.iter().map(PolicyId::new).collect();
369 let annotations = self.instance.annotations_map(ids.iter());
370 to_object_recursive(serde_wasm_bindgen::to_value(&annotations)?)
371 }
372
373 /// Collect every value of the annotation `key` across the given policies,
374 /// preserving duplicates. Unknown policy IDs are silently skipped.
375 ///
376 /// # Arguments
377 ///
378 /// * `policy_ids` - List of policy IDs to search. Typically
379 /// `result.response.diagnostics.reason` from an authorization result.
380 /// * `key` - The annotation key to collect values for (e.g. `"redirect"`).
381 ///
382 /// # Example
383 ///
384 /// ```javascript
385 /// const redirects = cedarling.annotationValues(
386 /// result.response.diagnostics.reason,
387 /// "redirect",
388 /// );
389 /// // ["/upgrade"]
390 /// ```
391 #[wasm_bindgen(js_name = annotationValues)]
392 pub fn annotation_values(&self, policy_ids: Vec<String>, key: &str) -> Vec<String> {
393 let ids: Vec<PolicyId> = policy_ids.iter().map(PolicyId::new).collect();
394 self.instance.annotation_values(ids.iter(), key)
395 }
396
397 /// Return the annotations of each given policy, grouped by policy ID. It is
398 /// the loss-free companion to `annotationsMap`. Unknown policy IDs are
399 /// silently skipped.
400 ///
401 /// # Arguments
402 ///
403 /// * `policy_ids` - List of policy IDs whose annotations should be returned
404 /// grouped by policy ID. Typically `result.response.diagnostics.reason` from
405 /// an authorization result.
406 ///
407 /// # Example
408 ///
409 /// ```javascript
410 /// const byPolicy = cedarling.annotationsByPolicy(result.response.diagnostics.reason);
411 /// // { "5": { redirect: "/upgrade", tier: "premium" } }
412 /// ```
413 #[wasm_bindgen(js_name = annotationsByPolicy)]
414 pub fn annotations_by_policy(&self, policy_ids: Vec<String>) -> Result<JsValue, Error> {
415 let ids: Vec<PolicyId> = policy_ids.iter().map(PolicyId::new).collect();
416 let by_policy = self.instance.annotations_by_policy(ids.iter());
417 to_object_recursive(serde_wasm_bindgen::to_value(&by_policy)?)
418 }
419
420 /// Returns all retained memory logs and removes them from storage.
421 /// Other log configurations return an empty array.
422 ///
423 /// # Arguments
424 ///
425 /// None.
426 ///
427 /// # Example
428 ///
429 /// ```javascript
430 /// const logs = cedarling.popLogs();
431 /// ```
432 #[wasm_bindgen(js_name = popLogs)]
433 pub fn pop_logs(&self) -> Result<Array, Error> {
434 let result = Array::new();
435 for log in self.instance.pop_logs() {
436 let js_log = convert_json_to_object(&log)?;
437 result.push(&js_log);
438 }
439 Ok(result)
440 }
441
442 /// Returns one retained memory log by ID, or `null` when it is not retained.
443 ///
444 /// # Arguments
445 ///
446 /// * `id` - The retained log identifier.
447 ///
448 /// # Example
449 ///
450 /// ```javascript
451 /// const log = cedarling.getLogById("request-id");
452 /// ```
453 #[wasm_bindgen(js_name = getLogById)]
454 pub fn get_log_by_id(&self, id: &str) -> Result<JsValue, Error> {
455 let result = if let Some(log_json_value) = self.instance.get_log_by_id(id) {
456 convert_json_to_object(&log_json_value)?
457 } else {
458 JsValue::NULL
459 };
460 Ok(result)
461 }
462
463 /// Returns identifiers for all retained memory logs.
464 ///
465 /// # Arguments
466 ///
467 /// None.
468 ///
469 /// # Example
470 ///
471 /// ```javascript
472 /// const ids = cedarling.getLogIds();
473 /// ```
474 #[wasm_bindgen(js_name = getLogIds)]
475 pub fn get_log_ids(&self) -> Array {
476 let result = Array::new();
477 for log_id in self.instance.get_log_ids() {
478 let js_id = log_id.into();
479 result.push(&js_id);
480 }
481 result
482 }
483
484 /// Returns retained memory logs matching an indexed value.
485 ///
486 /// # Arguments
487 ///
488 /// * `tag` - A log kind (`"System"`, `"Decision"`, or `"Metric"`) or a
489 /// system-log level such as `"DEBUG"`.
490 ///
491 /// # Example
492 ///
493 /// ```javascript
494 /// const logs = cedarling.getLogsByTag("System");
495 /// ```
496 #[wasm_bindgen(js_name = getLogsByTag)]
497 pub fn get_logs_by_tag(&self, tag: &str) -> Result<Vec<JsValue>, Error> {
498 self.instance
499 .get_logs_by_tag(tag)
500 .iter()
501 .map(convert_json_to_object)
502 .collect()
503 }
504
505 /// Returns retained memory logs for one request ID.
506 ///
507 /// # Arguments
508 ///
509 /// * `requestId` - The request identifier to match.
510 ///
511 /// # Example
512 ///
513 /// ```javascript
514 /// const logs = cedarling.getLogsByRequestId("request-id");
515 /// ```
516 #[wasm_bindgen(js_name = getLogsByRequestId)]
517 pub fn get_logs_by_request_id(&self, request_id: &str) -> Result<Vec<JsValue>, Error> {
518 self.instance
519 .get_logs_by_request_id(request_id)
520 .iter()
521 .map(convert_json_to_object)
522 .collect()
523 }
524
525 /// Returns retained memory logs matching one request ID and indexed value.
526 ///
527 /// # Arguments
528 ///
529 /// * `requestId` - The request identifier to match.
530 /// * `tag` - A log kind (`"System"`, `"Decision"`, or `"Metric"`) or a
531 /// system-log level such as `"DEBUG"`.
532 ///
533 /// # Example
534 ///
535 /// ```javascript
536 /// const logs = cedarling.getLogsByRequestIdAndTag("request-id", "System");
537 /// ```
538 #[wasm_bindgen(js_name = getLogsByRequestIdAndTag)]
539 pub fn get_logs_by_request_id_and_tag(
540 &self,
541 request_id: &str,
542 tag: &str,
543 ) -> Result<Vec<JsValue>, Error> {
544 self.instance
545 .get_logs_by_request_id_and_tag(request_id, tag)
546 .iter()
547 .map(convert_json_to_object)
548 .collect()
549 }
550
551 /// Closes Lock Server connections and pushes all available logs.
552 ///
553 /// # Arguments
554 ///
555 /// None.
556 ///
557 /// # Example
558 ///
559 /// ```javascript
560 /// await cedarling.shutDown();
561 /// ```
562 #[wasm_bindgen(js_name = shutDown)]
563 pub async fn shut_down(&self) {
564 self.instance.shut_down().await;
565 }
566
567 /// Push a value into the data store with an optional TTL.
568 /// If the key already exists, the value will be replaced.
569 /// If TTL is not provided, the default TTL from configuration is used.
570 ///
571 /// # Arguments
572 ///
573 /// * `key` - A string key for the data entry (must not be empty)
574 /// * `value` - The value to store (any JSON-serializable JavaScript value: object, array, string, number, boolean)
575 /// * `ttl_secs` - Optional TTL in seconds (undefined/null uses default from config)
576 ///
577 /// # Example
578 ///
579 /// ```javascript
580 /// cedarling.pushDataCtx("user:123", { name: "John", age: 30 }, 3600);
581 /// cedarling.pushDataCtx("config", { setting: "value" }); // Uses default TTL
582 /// ```
583 #[wasm_bindgen(js_name = pushDataCtx)]
584 pub fn push_data_ctx(
585 &self,
586 key: &str,
587 value: JsValue,
588 ttl_secs: Option<u64>,
589 ) -> Result<(), Error> {
590 let json_value: serde_json::Value = serde_wasm_bindgen::from_value(value)?;
591
592 // Reject null values on write
593 if json_value.is_null() {
594 return Err(Error::new("null values are not allowed in data ctx"));
595 }
596
597 let ttl = ttl_secs.map(Duration::from_secs);
598 self.instance
599 .push_data_ctx(key, json_value, ttl)
600 .map_err(Error::new)
601 }
602
603 /// Get a value from the data store by key.
604 /// Returns null if the key doesn't exist or the entry has expired.
605 ///
606 /// # Arguments
607 ///
608 /// * `key` - A string key for the data entry to retrieve
609 ///
610 /// # Example
611 ///
612 /// ```javascript
613 /// const value = cedarling.getDataCtx("user:123");
614 /// if (value !== null) {
615 /// console.log(value.name); // "John"
616 /// }
617 /// ```
618 #[wasm_bindgen(js_name = getDataCtx)]
619 pub fn get_data_ctx(&self, key: &str) -> Result<JsValue, Error> {
620 match self.instance.get_data_ctx(key).map_err(Error::new)? {
621 Some(value) => {
622 let js_value = serde_wasm_bindgen::to_value(&value)?;
623 Ok(to_object_recursive(js_value)?)
624 },
625 None => Ok(JsValue::NULL),
626 }
627 }
628
629 /// Get a data entry with full metadata by key.
630 /// Returns undefined if the key doesn't exist or the entry has expired.
631 ///
632 /// # Arguments
633 ///
634 /// * `key` - A string key for the data entry to retrieve
635 ///
636 /// # Example
637 ///
638 /// ```javascript
639 /// const entry = cedarling.getDataEntryCtx("user:123");
640 /// if (entry !== undefined) {
641 /// console.log(entry.key); // "user:123"
642 /// console.log(entry.value()); // { name: "John", age: 30 }
643 /// console.log(entry.data_type); // "Record"
644 /// console.log(entry.created_at); // "2024-01-01T12:00:00Z"
645 /// console.log(entry.access_count); // 5
646 /// }
647 /// ```
648 #[wasm_bindgen(js_name = getDataEntryCtx)]
649 pub fn get_data_entry_ctx(&self, key: &str) -> Result<Option<DataEntry>, Error> {
650 match self.instance.get_data_entry_ctx(key).map_err(Error::new)? {
651 Some(entry) => {
652 let wasm_entry = DataEntry::from(entry);
653 Ok(Some(wasm_entry))
654 },
655 None => Ok(None),
656 }
657 }
658
659 /// Remove a value from the data store by key.
660 /// Returns true if the key existed and was removed, false otherwise.
661 ///
662 /// # Arguments
663 ///
664 /// * `key` - A string key for the data entry to remove
665 ///
666 /// # Example
667 ///
668 /// ```javascript
669 /// const removed = cedarling.removeDataCtx("user:123");
670 /// if (removed) {
671 /// console.log("Entry was successfully removed");
672 /// }
673 /// ```
674 #[wasm_bindgen(js_name = removeDataCtx)]
675 pub fn remove_data_ctx(&self, key: &str) -> Result<bool, Error> {
676 self.instance.remove_data_ctx(key).map_err(Error::new)
677 }
678
679 /// Clear all entries from the data store.
680 ///
681 /// # Arguments
682 ///
683 /// None.
684 ///
685 /// # Example
686 ///
687 /// ```javascript
688 /// cedarling.clearDataCtx();
689 /// console.log("All data entries cleared");
690 /// ```
691 #[wasm_bindgen(js_name = clearDataCtx)]
692 pub fn clear_data_ctx(&self) -> Result<(), Error> {
693 self.instance.clear_data_ctx().map_err(Error::new)
694 }
695
696 /// List all entries with their metadata.
697 /// Returns an array of DataEntry objects.
698 ///
699 /// # Arguments
700 ///
701 /// None.
702 ///
703 /// # Example
704 ///
705 /// ```javascript
706 /// const entries = cedarling.listDataCtx();
707 /// entries.forEach(entry => {
708 /// console.log(`${entry.key}: ${entry.data_type} (accessed ${entry.access_count} times)`);
709 /// });
710 /// ```
711 #[wasm_bindgen(js_name = listDataCtx)]
712 pub fn list_data_ctx(&self) -> Result<Array, Error> {
713 let entries = self.instance.list_data_ctx().map_err(Error::new)?;
714 let result = Array::new();
715 for entry in entries {
716 let wasm_entry = DataEntry::from(entry);
717 result.push(&wasm_bindgen::JsValue::from(wasm_entry));
718 }
719 Ok(result)
720 }
721
722 /// Get statistics about the data store.
723 ///
724 /// # Arguments
725 ///
726 /// None.
727 ///
728 /// # Example
729 ///
730 /// ```javascript
731 /// const stats = cedarling.getStatsCtx();
732 /// console.log(`Entries: ${stats.entry_count}/${stats.max_entries || 'unlimited'}`);
733 /// console.log(`Capacity: ${stats.capacity_usage_percent.toFixed(2)}%`);
734 /// console.log(`Total size: ${stats.total_size_bytes} bytes`);
735 /// ```
736 #[wasm_bindgen(js_name = getStatsCtx)]
737 pub fn get_stats_ctx(&self) -> Result<DataStoreStats, Error> {
738 self.instance
739 .get_stats_ctx()
740 .map(|stats| stats.into())
741 .map_err(Error::new)
742 }
743
744 /// Check whether a trusted issuer was loaded by issuer identifier.
745 ///
746 /// # Arguments
747 ///
748 /// * `issuer_id` - Trusted issuer identifier to check.
749 ///
750 /// # Example
751 ///
752 /// ```javascript
753 /// const ok = cedarling.isTrustedIssuerLoadedByName("issuer_id");
754 /// ```
755 #[wasm_bindgen(js_name = isTrustedIssuerLoadedByName)]
756 pub fn is_trusted_issuer_loaded_by_name(&self, issuer_id: &str) -> bool {
757 self.instance.is_trusted_issuer_loaded_by_name(issuer_id)
758 }
759
760 /// Check whether a trusted issuer was loaded by `iss` claim.
761 ///
762 /// # Arguments
763 ///
764 /// * `iss_claim` - Issuer `iss` claim value to check.
765 ///
766 /// # Example
767 ///
768 /// ```javascript
769 /// const ok = cedarling.isTrustedIssuerLoadedByIss("https://issuer.example.org");
770 /// ```
771 #[wasm_bindgen(js_name = isTrustedIssuerLoadedByIss)]
772 pub fn is_trusted_issuer_loaded_by_iss(&self, iss_claim: &str) -> bool {
773 self.instance.is_trusted_issuer_loaded_by_iss(iss_claim)
774 }
775
776 /// Get the total number of trusted issuer entries discovered.
777 ///
778 /// # Arguments
779 ///
780 /// None.
781 ///
782 /// # Example
783 ///
784 /// ```javascript
785 /// const total = cedarling.totalIssuers();
786 /// ```
787 #[wasm_bindgen(js_name = totalIssuers)]
788 pub fn total_issuers(&self) -> usize {
789 self.instance.total_issuers()
790 }
791
792 /// Get the number of trusted issuers loaded successfully.
793 ///
794 /// # Arguments
795 ///
796 /// None.
797 ///
798 /// # Example
799 ///
800 /// ```javascript
801 /// const loadedCount = cedarling.loadedTrustedIssuersCount();
802 /// ```
803 #[wasm_bindgen(js_name = loadedTrustedIssuersCount)]
804 pub fn loaded_trusted_issuers_count(&self) -> usize {
805 self.instance.loaded_trusted_issuers_count()
806 }
807
808 /// Get trusted issuer identifiers loaded successfully.
809 ///
810 /// # Arguments
811 ///
812 /// None.
813 ///
814 /// # Example
815 ///
816 /// ```javascript
817 /// const ids = cedarling.loadedTrustedIssuerIds();
818 /// ```
819 #[wasm_bindgen(js_name = loadedTrustedIssuerIds)]
820 pub fn loaded_trusted_issuer_ids(&self) -> Array {
821 let result = Array::new();
822 for id in self.instance.loaded_trusted_issuer_ids() {
823 result.push(&id.into());
824 }
825 result
826 }
827
828 /// Get trusted issuer identifiers that failed to load.
829 ///
830 /// # Arguments
831 ///
832 /// None.
833 ///
834 /// # Example
835 ///
836 /// ```javascript
837 /// const ids = cedarling.failedTrustedIssuerIds();
838 /// ```
839 #[wasm_bindgen(js_name = failedTrustedIssuerIds)]
840 pub fn failed_trusted_issuer_ids(&self) -> Array {
841 let result = Array::new();
842 for id in self.instance.failed_trusted_issuer_ids() {
843 result.push(&id.into());
844 }
845 result
846 }
847}
848
849/// convert json to js object
850fn convert_json_to_object(json_value: &serde_json::Value) -> Result<JsValue, Error> {
851 let js_map_value = serde_wasm_bindgen::to_value(json_value)?;
852 to_object_recursive(js_map_value)
853}
854
855/// recurcive convert [`Map`] to object
856fn to_object_recursive(value: JsValue) -> Result<JsValue, Error> {
857 if value.is_instance_of::<Map>() {
858 // Convert the Map into an Object where keys and values are recursively processed
859 let map = Map::from(value);
860 let obj = Object::new();
861 for entry in map.entries().into_iter() {
862 let entry = Array::unchecked_from_js(entry?);
863 let key = entry.get(0);
864 let val = to_object_recursive(entry.get(1))?;
865 Reflect::set(&obj, &key, &val)?;
866 }
867 Ok(obj.into())
868 } else if value.is_instance_of::<Array>() {
869 // Recursively process arrays
870 let array = Array::unchecked_from_js(value);
871 let serialized_array = Array::new();
872 for item in array.iter() {
873 serialized_array.push(&to_object_recursive(item)?);
874 }
875 Ok(serialized_array.into())
876 } else if value.is_object() {
877 // Recursively process plain objects
878 let obj = Object::from(value);
879 let keys = Object::keys(&obj);
880 let serialized_obj = Object::new();
881 for key in keys.iter() {
882 let val = Reflect::get(&obj, &key)?;
883 Reflect::set(&serialized_obj, &key, &to_object_recursive(val)?)?;
884 }
885 Ok(serialized_obj.into())
886 } else {
887 // Return primitive values as-is
888 Ok(value)
889 }
890}
891
892/// A WASM wrapper for the Rust `cedarling::AuthorizeResult` struct.
893/// Represents the result of an authorization request.
894#[wasm_bindgen]
895#[derive(Clone, Debug, serde::Serialize)]
896pub struct AuthorizeResult {
897 /// Cedar authorization response for the request.
898 #[wasm_bindgen(getter_with_clone)]
899 pub response: AuthorizeResultResponse,
900
901 /// Result of authorization
902 /// true means `ALLOW`
903 /// false means `Deny`
904 ///
905 /// this field is [`bool`] type to be compatible with [authzen Access Evaluation Decision](https://openid.github.io/authzen/#section-6.2.1).
906 pub decision: bool,
907
908 /// Request ID of the authorization request
909 #[wasm_bindgen(getter_with_clone)]
910 pub request_id: String,
911}
912
913#[wasm_bindgen]
914impl AuthorizeResult {
915 /// Convert `AuthorizeResult` to json string value
916 #[wasm_bindgen(js_name = jsonString)]
917 pub fn json_string(&self) -> String {
918 json!(self).to_string()
919 }
920}
921
922impl From<cedarling::AuthorizeResult> for AuthorizeResult {
923 fn from(value: cedarling::AuthorizeResult) -> Self {
924 Self {
925 response: AuthorizeResultResponse {
926 inner: Rc::new(value.response),
927 },
928 decision: value.decision,
929 request_id: value.request_id,
930 }
931 }
932}
933
934/// Per-item build failure surfaced inside a batch response at `results[i]`
935/// when Cedar couldn't be reached for that item.
936#[wasm_bindgen]
937#[derive(Clone, Debug)]
938pub struct BatchItemError {
939 inner: CedarBatchItemError,
940}
941
942#[wasm_bindgen]
943impl BatchItemError {
944 /// Stable variant slug — `action_parse`, `resource_build`, `context_build`,
945 /// `principal_build`, `schema_validation`, `multi_issuer_entity`,
946 /// `request_validation`.
947 #[wasm_bindgen(getter)]
948 pub fn category(&self) -> String {
949 self.inner.category().to_string()
950 }
951
952 /// Position of the failing item in the original `items` vector.
953 #[wasm_bindgen(getter)]
954 pub fn item_index(&self) -> usize {
955 self.inner.item_index()
956 }
957
958 /// Human-readable diagnostic. Safe to log.
959 #[wasm_bindgen(getter)]
960 pub fn message(&self) -> String {
961 self.inner.to_string()
962 }
963}
964
965impl From<CedarBatchItemError> for BatchItemError {
966 fn from(inner: CedarBatchItemError) -> Self {
967 Self { inner }
968 }
969}
970
971/// One slot in a batch response's `results` array. Callers switch on
972/// `is_ok()` — on `true`, read `unwrap()`; on `false`, read `error()`.
973#[wasm_bindgen]
974#[derive(Clone, Debug)]
975pub struct BatchItemUnsignedResult {
976 inner: Result<AuthorizeResult, BatchItemError>,
977}
978
979#[wasm_bindgen]
980impl BatchItemUnsignedResult {
981 /// `true` when Cedar evaluated this item (Allow or Deny); `false` when it
982 /// failed to build.
983 #[wasm_bindgen(getter)]
984 pub fn is_ok(&self) -> bool {
985 self.inner.is_ok()
986 }
987
988 /// The Cedar decision if `is_ok()`; throws otherwise.
989 pub fn unwrap(&self) -> Result<AuthorizeResult, Error> {
990 self.inner
991 .as_ref()
992 .cloned()
993 .map_err(|_| Error::new("BatchItemUnsignedResult is Err"))
994 }
995
996 /// The per-item error if `!is_ok()`; `undefined` otherwise.
997 #[wasm_bindgen(getter)]
998 pub fn error(&self) -> Option<BatchItemError> {
999 self.inner.as_ref().err().cloned()
1000 }
1001}
1002
1003impl From<Result<cedarling::AuthorizeResult, CedarBatchItemError>> for BatchItemUnsignedResult {
1004 fn from(r: Result<cedarling::AuthorizeResult, CedarBatchItemError>) -> Self {
1005 Self {
1006 inner: r.map(Into::into).map_err(Into::into),
1007 }
1008 }
1009}
1010
1011/// Multi-issuer analog of [`BatchItemUnsignedResult`].
1012#[wasm_bindgen]
1013#[derive(Clone, Debug)]
1014pub struct BatchItemMultiIssuerResult {
1015 inner: Result<MultiIssuerAuthorizeResult, BatchItemError>,
1016}
1017
1018#[wasm_bindgen]
1019impl BatchItemMultiIssuerResult {
1020 /// `true` when Cedar evaluated this item.
1021 #[wasm_bindgen(getter)]
1022 pub fn is_ok(&self) -> bool {
1023 self.inner.is_ok()
1024 }
1025
1026 /// The multi-issuer decision if `is_ok()`; throws otherwise.
1027 pub fn unwrap(&self) -> Result<MultiIssuerAuthorizeResult, Error> {
1028 self.inner
1029 .as_ref()
1030 .cloned()
1031 .map_err(|_| Error::new("BatchItemMultiIssuerResult is Err"))
1032 }
1033
1034 /// The per-item error if `!is_ok()`; `undefined` otherwise.
1035 #[wasm_bindgen(getter)]
1036 pub fn error(&self) -> Option<BatchItemError> {
1037 self.inner.as_ref().err().cloned()
1038 }
1039}
1040
1041impl From<Result<cedarling::MultiIssuerAuthorizeResult, CedarBatchItemError>>
1042 for BatchItemMultiIssuerResult
1043{
1044 fn from(r: Result<cedarling::MultiIssuerAuthorizeResult, CedarBatchItemError>) -> Self {
1045 Self {
1046 inner: r.map(Into::into).map_err(Into::into),
1047 }
1048 }
1049}
1050
1051/// WASM wrapper for `cedarling::BatchAuthorizeResponse<Result<AuthorizeResult, BatchItemError>>`.
1052///
1053/// Carries a shared `batch_id` (UUIDv7) alongside per-item results. Each result
1054/// is a [`BatchItemUnsignedResult`] — Cedar decision on `is_ok()`, per-item
1055/// build failure on `error`. `results[i]` corresponds to `items[i]`.
1056#[wasm_bindgen]
1057#[derive(Debug)]
1058pub struct BatchAuthorizeUnsignedResponse {
1059 inner_batch_id: String,
1060 inner_results: Vec<BatchItemUnsignedResult>,
1061}
1062
1063#[wasm_bindgen]
1064impl BatchAuthorizeUnsignedResponse {
1065 /// Shared correlation id stamped on every per-item decision log entry.
1066 #[wasm_bindgen(getter)]
1067 pub fn batch_id(&self) -> String {
1068 self.inner_batch_id.clone()
1069 }
1070
1071 /// Per-item results in input order — each slot is a
1072 /// [`BatchItemUnsignedResult`].
1073 #[wasm_bindgen(getter)]
1074 pub fn results(&self) -> Vec<BatchItemUnsignedResult> {
1075 self.inner_results.clone()
1076 }
1077}
1078
1079impl From<CedarBatchAuthorizeResponse<Result<cedarling::AuthorizeResult, CedarBatchItemError>>>
1080 for BatchAuthorizeUnsignedResponse
1081{
1082 fn from(
1083 value: CedarBatchAuthorizeResponse<Result<cedarling::AuthorizeResult, CedarBatchItemError>>,
1084 ) -> Self {
1085 Self {
1086 inner_batch_id: value.batch_id.to_string(),
1087 inner_results: value.results.into_iter().map(Into::into).collect(),
1088 }
1089 }
1090}
1091
1092/// WASM wrapper for
1093/// `cedarling::BatchAuthorizeResponse<Result<MultiIssuerAuthorizeResult, BatchItemError>>`.
1094/// Same shape as [`BatchAuthorizeUnsignedResponse`] with multi-issuer results.
1095#[wasm_bindgen]
1096#[derive(Debug)]
1097pub struct BatchAuthorizeMultiIssuerResponse {
1098 inner_batch_id: String,
1099 inner_results: Vec<BatchItemMultiIssuerResult>,
1100}
1101
1102#[wasm_bindgen]
1103impl BatchAuthorizeMultiIssuerResponse {
1104 /// Shared correlation id stamped on every per-item decision log entry.
1105 #[wasm_bindgen(getter)]
1106 pub fn batch_id(&self) -> String {
1107 self.inner_batch_id.clone()
1108 }
1109
1110 /// Per-item results in input order — each slot is a
1111 /// [`BatchItemMultiIssuerResult`].
1112 #[wasm_bindgen(getter)]
1113 pub fn results(&self) -> Vec<BatchItemMultiIssuerResult> {
1114 self.inner_results.clone()
1115 }
1116}
1117
1118impl
1119 From<
1120 CedarBatchAuthorizeResponse<
1121 Result<cedarling::MultiIssuerAuthorizeResult, CedarBatchItemError>,
1122 >,
1123 > for BatchAuthorizeMultiIssuerResponse
1124{
1125 fn from(
1126 value: CedarBatchAuthorizeResponse<
1127 Result<cedarling::MultiIssuerAuthorizeResult, CedarBatchItemError>,
1128 >,
1129 ) -> Self {
1130 Self {
1131 inner_batch_id: value.batch_id.to_string(),
1132 inner_results: value.results.into_iter().map(Into::into).collect(),
1133 }
1134 }
1135}
1136
1137/// A WASM wrapper for the Rust `cedar_policy::Response` struct.
1138/// Represents the result of an authorization request.
1139#[wasm_bindgen]
1140#[derive(Clone, Debug)]
1141pub struct AuthorizeResultResponse {
1142 // It can be premature optimization, but RC allows avoiding clone actual structure
1143 inner: Rc<cedar_policy::Response>,
1144}
1145
1146#[wasm_bindgen]
1147impl AuthorizeResultResponse {
1148 /// Authorization decision
1149 #[wasm_bindgen(getter)]
1150 pub fn decision(&self) -> bool {
1151 self.inner.decision() == cedar_policy::Decision::Allow
1152 }
1153
1154 /// Diagnostics providing more information on how this decision was reached
1155 #[wasm_bindgen(getter)]
1156 pub fn diagnostics(&self) -> Diagnostics {
1157 Diagnostics {
1158 inner: self.inner.diagnostics().clone(),
1159 }
1160 }
1161}
1162
1163impl Serialize for AuthorizeResultResponse {
1164 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1165 where
1166 S: Serializer,
1167 {
1168 let mut state = serializer.serialize_struct("Diagnostics", 2)?;
1169 state.serialize_field("decision", &self.decision())?;
1170 state.serialize_field("diagnostics", &self.diagnostics())?;
1171 state.end()
1172 }
1173}
1174
1175/// Diagnostics
1176/// ===========
1177///
1178/// Provides detailed information about how a policy decision was made, including policies that contributed to the decision and any errors encountered during evaluation.
1179#[wasm_bindgen]
1180pub struct Diagnostics {
1181 inner: cedar_policy::Diagnostics,
1182}
1183
1184#[wasm_bindgen]
1185impl Diagnostics {
1186 /// `PolicyId`s of the policies that contributed to the decision.
1187 /// If no policies applied to the request, this set will be empty.
1188 ///
1189 /// The ids should be treated as unordered,
1190 #[wasm_bindgen(getter)]
1191 pub fn reason(&self) -> Vec<String> {
1192 self.inner.reason().map(|v| v.to_string()).collect()
1193 }
1194
1195 /// Errors that occurred during authorization. The errors should be
1196 /// treated as unordered, since policies may be evaluated in any order.
1197 #[wasm_bindgen(getter)]
1198 pub fn errors(&self) -> Vec<PolicyEvaluationError> {
1199 self.inner
1200 .errors()
1201 .map(|err| {
1202 let mapped_error: cedarling::bindings::PolicyEvaluationError = err.into();
1203 PolicyEvaluationError {
1204 inner: mapped_error,
1205 }
1206 })
1207 .collect()
1208 }
1209}
1210
1211impl Serialize for Diagnostics {
1212 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1213 where
1214 S: Serializer,
1215 {
1216 let mut state = serializer.serialize_struct("Diagnostics", 2)?;
1217 state.serialize_field("reason", &self.reason())?;
1218 state.serialize_field("errors", &self.errors())?;
1219 state.end()
1220 }
1221}
1222
1223/// PolicyEvaluationError
1224/// =====================
1225///
1226/// Represents an error that occurred when evaluating a Cedar policy.
1227#[wasm_bindgen]
1228pub struct PolicyEvaluationError {
1229 inner: cedarling::bindings::PolicyEvaluationError,
1230}
1231
1232#[wasm_bindgen]
1233impl PolicyEvaluationError {
1234 /// Id of the policy with an error
1235 #[wasm_bindgen(getter)]
1236 pub fn id(&self) -> String {
1237 self.inner.id.clone()
1238 }
1239
1240 /// Underlying evaluation error string representation
1241 #[wasm_bindgen(getter)]
1242 pub fn error(&self) -> String {
1243 self.inner.error.clone()
1244 }
1245}
1246
1247impl Serialize for PolicyEvaluationError {
1248 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1249 where
1250 S: Serializer,
1251 {
1252 let mut state = serializer.serialize_struct("PolicyEvaluationError", 2)?;
1253 state.serialize_field("id", &self.id())?;
1254 state.serialize_field("error", &self.error())?;
1255 state.end()
1256 }
1257}
1258
1259/// A WASM wrapper for the Rust `cedarling::DataEntry` struct.
1260/// Represents a data entry in the DataStore with value and metadata.
1261#[wasm_bindgen]
1262#[derive(serde::Serialize)]
1263pub struct DataEntry {
1264 /// The key for this entry
1265 #[wasm_bindgen(getter_with_clone)]
1266 pub key: String,
1267 /// The actual value stored (as JSON)
1268 #[wasm_bindgen(skip)]
1269 pub value: serde_json::Value,
1270 /// The inferred Cedar type of the value
1271 #[wasm_bindgen(getter_with_clone)]
1272 pub data_type: String,
1273 /// Timestamp when this entry was created (RFC 3339 format)
1274 #[wasm_bindgen(getter_with_clone)]
1275 pub created_at: String,
1276 /// Timestamp when this entry expires (RFC 3339 format). The getter returns undefined if no TTL.
1277 #[wasm_bindgen(getter_with_clone)]
1278 pub expires_at: Option<String>,
1279 /// Number of times this entry has been accessed
1280 pub access_count: u64,
1281}
1282
1283#[wasm_bindgen]
1284impl DataEntry {
1285 /// Get the value stored in this entry as a JavaScript object
1286 pub fn value(&self) -> Result<JsValue, Error> {
1287 let js_value = serde_wasm_bindgen::to_value(&self.value)?;
1288 to_object_recursive(js_value)
1289 }
1290
1291 /// Convert `DataEntry` to json string value
1292 #[wasm_bindgen(js_name = jsonString)]
1293 pub fn json_string(&self) -> String {
1294 json!(self).to_string()
1295 }
1296}
1297
1298impl From<CedarDataEntry> for DataEntry {
1299 fn from(value: CedarDataEntry) -> Self {
1300 Self {
1301 key: value.key,
1302 value: value.value,
1303 data_type: serde_json::to_string(&value.data_type)
1304 .unwrap_or_else(|_| "unknown".to_string())
1305 .trim_matches('"')
1306 .to_string(),
1307 created_at: value.created_at.to_rfc3339(),
1308 expires_at: value.expires_at.map(|dt| dt.to_rfc3339()),
1309 access_count: value.access_count,
1310 }
1311 }
1312}
1313
1314/// A WASM wrapper for the Rust `cedarling::DataStoreStats` struct.
1315/// Statistics about the DataStore.
1316#[wasm_bindgen]
1317#[derive(serde::Serialize)]
1318pub struct DataStoreStats {
1319 /// Number of entries currently stored
1320 pub entry_count: usize,
1321 /// Maximum number of entries allowed (0 = unlimited)
1322 pub max_entries: usize,
1323 /// Maximum size per entry in bytes (0 = unlimited)
1324 pub max_entry_size: usize,
1325 /// Whether metrics tracking is enabled
1326 pub metrics_enabled: bool,
1327 /// Total size of all entries in bytes (approximate, based on JSON serialization)
1328 pub total_size_bytes: usize,
1329 /// Average size per entry in bytes (0 if no entries)
1330 pub avg_entry_size_bytes: usize,
1331 /// Percentage of capacity used (0.0-100.0, based on entry count)
1332 pub capacity_usage_percent: f64,
1333 /// Memory usage threshold percentage (from config)
1334 pub memory_alert_threshold: f64,
1335 /// Whether memory usage exceeds the alert threshold
1336 pub memory_alert_triggered: bool,
1337}
1338
1339#[wasm_bindgen]
1340impl DataStoreStats {
1341 /// Convert `DataStoreStats` to json string value
1342 #[wasm_bindgen(js_name = jsonString)]
1343 pub fn json_string(&self) -> String {
1344 json!(self).to_string()
1345 }
1346}
1347
1348impl From<CedarDataStoreStats> for DataStoreStats {
1349 fn from(value: CedarDataStoreStats) -> Self {
1350 Self {
1351 entry_count: value.entry_count,
1352 max_entries: value.max_entries,
1353 max_entry_size: value.max_entry_size,
1354 metrics_enabled: value.metrics_enabled,
1355 total_size_bytes: value.total_size_bytes,
1356 avg_entry_size_bytes: value.avg_entry_size_bytes,
1357 capacity_usage_percent: value.capacity_usage_percent,
1358 memory_alert_threshold: value.memory_alert_threshold,
1359 memory_alert_triggered: value.memory_alert_triggered,
1360 }
1361 }
1362}