| 99 | } |
| 100 | |
| 101 | pub fn from_bytes(buf: &[u8]) -> Result<Self> { |
| 102 | if buf.len() < 1 + 4 { |
| 103 | return Err(WalError::InvalidPayload { |
| 104 | detail: "temporal-purge payload shorter than engine_tag + name_len".into(), |
| 105 | }); |
| 106 | } |
| 107 | let engine = |
| 108 | TemporalPurgeEngine::from_raw(buf[0]).ok_or_else(|| WalError::InvalidPayload { |
| 109 | detail: format!("temporal-purge unknown engine_tag {}", buf[0]), |
| 110 | })?; |
| 111 | let name_len = u32::from_le_bytes([buf[1], buf[2], buf[3], buf[4]]) as usize; |
| 112 | if name_len > MAX_COLLECTION_NAME_LEN { |
| 113 | return Err(WalError::InvalidPayload { |
| 114 | detail: format!("temporal-purge name_len {name_len} exceeds max"), |
| 115 | }); |
| 116 | } |
| 117 | let need = 1 + 4 + name_len + 8 + 8; |
| 118 | if buf.len() < need { |
| 119 | return Err(WalError::InvalidPayload { |
| 120 | detail: format!( |
| 121 | "temporal-purge payload truncated: need {need} bytes, have {}", |
| 122 | buf.len() |
| 123 | ), |
| 124 | }); |
| 125 | } |
| 126 | let name_end = 5 + name_len; |
| 127 | let name = std::str::from_utf8(&buf[5..name_end]) |
| 128 | .map_err(|e| WalError::InvalidPayload { |
| 129 | detail: format!("temporal-purge name not utf8: {e}"), |
| 130 | })? |
| 131 | .to_string(); |
| 132 | let cutoff_system_ms = i64::from_le_bytes( |
| 133 | buf[name_end..name_end + 8] |
| 134 | .try_into() |
| 135 | .expect("bounded above"), |
| 136 | ); |
| 137 | let purged_count = u64::from_le_bytes( |
| 138 | buf[name_end + 8..name_end + 16] |
| 139 | .try_into() |
| 140 | .expect("bounded above"), |
| 141 | ); |
| 142 | Ok(Self { |
| 143 | engine, |
| 144 | name, |
| 145 | cutoff_system_ms, |
| 146 | purged_count, |
| 147 | }) |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | #[cfg(test)] |