Skip to main content

webui_protocol/plugin/
webui.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT license.
3
4use crate::{ProtocolError, Result};
5
6const WEBUI_ELEMENT_DATA_LEN: usize = 12;
7
8/// WebUI hydration element metadata encoded in plugin fragments.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct WebUIElementData {
11    /// Number of dynamic attribute bindings on the element.
12    pub binding_count: u32,
13    /// Starting event index for this element within the fragment-local event list.
14    pub event_start: u32,
15    /// Number of framework event handlers on the element.
16    pub event_count: u32,
17}
18
19impl WebUIElementData {
20    /// Encode this metadata using the WebUI 12-byte little-endian wire format.
21    #[must_use]
22    pub fn encode(self) -> [u8; WEBUI_ELEMENT_DATA_LEN] {
23        let mut data = [0u8; WEBUI_ELEMENT_DATA_LEN];
24        data[..4].copy_from_slice(&self.binding_count.to_le_bytes());
25        data[4..8].copy_from_slice(&self.event_start.to_le_bytes());
26        data[8..12].copy_from_slice(&self.event_count.to_le_bytes());
27        data
28    }
29
30    /// Decode WebUI hydration metadata from protocol bytes.
31    ///
32    /// # Errors
33    ///
34    /// Returns [`ProtocolError::Validation`] when the payload length is not 12 bytes.
35    pub fn decode(bytes: &[u8]) -> Result<Self> {
36        if bytes.len() != WEBUI_ELEMENT_DATA_LEN {
37            return Err(ProtocolError::Validation(format!(
38                "WebUI element data must be {WEBUI_ELEMENT_DATA_LEN} bytes, received {}",
39                bytes.len()
40            )));
41        }
42
43        Ok(Self {
44            binding_count: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
45            event_start: u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
46            event_count: u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
47        })
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::WebUIElementData;
54    use crate::ProtocolError;
55
56    #[test]
57    fn test_webui_element_data_roundtrip() {
58        let encoded = WebUIElementData {
59            binding_count: 2,
60            event_start: 5,
61            event_count: 1,
62        }
63        .encode();
64        let decoded = WebUIElementData::decode(&encoded).expect("decode should succeed");
65        assert_eq!(decoded.binding_count, 2);
66        assert_eq!(decoded.event_start, 5);
67        assert_eq!(decoded.event_count, 1);
68    }
69
70    #[test]
71    fn test_webui_element_data_rejects_invalid_length() {
72        let result = WebUIElementData::decode(&[1, 2, 3, 4]);
73        assert!(
74            matches!(result, Err(ProtocolError::Validation(ref msg)) if msg.contains("12 bytes")),
75            "invalid payload length should be rejected: {result:?}"
76        );
77    }
78}