iggy_binary_protocol/requests/users/
update_permissions.rs1use crate::WireError;
19use crate::WireIdentifier;
20use crate::codec::{WireDecode, WireEncode, read_u8, read_u32_le};
21use crate::primitives::permissions::WirePermissions;
22use bytes::{BufMut, BytesMut};
23use std::borrow::Cow;
24
25#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct UpdatePermissionsRequest {
31 pub user_id: WireIdentifier,
32 pub permissions: Option<WirePermissions>,
33}
34
35impl WireEncode for UpdatePermissionsRequest {
36 fn encoded_size(&self) -> usize {
37 self.user_id.encoded_size()
38 + 1 + self
40 .permissions
41 .as_ref()
42 .map_or(0, |p| 4 + p.encoded_size())
43 }
44
45 fn encode(&self, buf: &mut BytesMut) {
46 self.user_id.encode(buf);
47 if let Some(perms) = &self.permissions {
48 buf.put_u8(1);
49 let perm_bytes = perms.to_bytes();
50 #[allow(clippy::cast_possible_truncation)]
51 buf.put_u32_le(perm_bytes.len() as u32);
52 buf.put_slice(&perm_bytes);
53 } else {
54 buf.put_u8(0);
55 }
56 }
57}
58
59impl WireDecode for UpdatePermissionsRequest {
60 fn decode(buf: &[u8]) -> Result<(Self, usize), WireError> {
61 let (user_id, mut pos) = WireIdentifier::decode(buf)?;
62
63 let has_permissions = read_u8(buf, pos)?;
64 pos += 1;
65
66 let permissions = if has_permissions == 1 {
67 let perm_len = read_u32_le(buf, pos)? as usize;
68 pos += 4;
69 let (perms, consumed) = WirePermissions::decode(&buf[pos..])?;
70 if consumed != perm_len {
71 return Err(WireError::Validation(Cow::Owned(format!(
72 "permissions length mismatch: header says {perm_len}, decoded {consumed}"
73 ))));
74 }
75 pos += consumed;
76 Some(perms)
77 } else {
78 None
79 };
80
81 Ok((
82 Self {
83 user_id,
84 permissions,
85 },
86 pos,
87 ))
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use crate::primitives::permissions::{WireGlobalPermissions, WirePermissions};
95
96 fn sample_permissions() -> WirePermissions {
97 WirePermissions {
98 global: WireGlobalPermissions {
99 manage_servers: true,
100 read_servers: true,
101 manage_users: true,
102 read_users: true,
103 manage_streams: false,
104 read_streams: false,
105 manage_topics: false,
106 read_topics: false,
107 poll_messages: true,
108 send_messages: true,
109 },
110 streams: vec![],
111 }
112 }
113
114 #[test]
115 fn roundtrip_with_permissions() {
116 let req = UpdatePermissionsRequest {
117 user_id: WireIdentifier::numeric(1),
118 permissions: Some(sample_permissions()),
119 };
120 let bytes = req.to_bytes();
121 let (decoded, consumed) = UpdatePermissionsRequest::decode(&bytes).unwrap();
122 assert_eq!(consumed, bytes.len());
123 assert_eq!(decoded, req);
124 }
125
126 #[test]
127 fn roundtrip_without_permissions() {
128 let req = UpdatePermissionsRequest {
129 user_id: WireIdentifier::named("admin").unwrap(),
130 permissions: None,
131 };
132 let bytes = req.to_bytes();
133 let (decoded, consumed) = UpdatePermissionsRequest::decode(&bytes).unwrap();
134 assert_eq!(consumed, bytes.len());
135 assert_eq!(decoded, req);
136 }
137
138 #[test]
139 fn encoded_size_matches_output() {
140 let req = UpdatePermissionsRequest {
141 user_id: WireIdentifier::numeric(42),
142 permissions: Some(sample_permissions()),
143 };
144 assert_eq!(req.encoded_size(), req.to_bytes().len());
145 }
146
147 #[test]
148 fn truncated_returns_error() {
149 let req = UpdatePermissionsRequest {
150 user_id: WireIdentifier::numeric(1),
151 permissions: Some(sample_permissions()),
152 };
153 let bytes = req.to_bytes();
154 for i in 0..bytes.len() {
155 assert!(
156 UpdatePermissionsRequest::decode(&bytes[..i]).is_err(),
157 "expected error for truncation at byte {i}"
158 );
159 }
160 }
161}