|
15 | 15 | //! Implementation for a _relational linked chunk_, see
|
16 | 16 | //! [`RelationalLinkedChunk`].
|
17 | 17 |
|
| 18 | +use std::ops::ControlFlow; |
| 19 | + |
18 | 20 | use ruma::{OwnedRoomId, RoomId};
|
19 | 21 |
|
20 | 22 | use super::{ChunkContent, ChunkIdentifierGenerator, RawChunk};
|
@@ -345,13 +347,36 @@ where
|
345 | 347 | };
|
346 | 348 |
|
347 | 349 | // Find the last chunk.
|
348 |
| - let Some(chunk_row) = self |
349 |
| - .chunks |
350 |
| - .iter() |
351 |
| - .find(|chunk_row| chunk_row.room_id == room_id && chunk_row.next_chunk.is_none()) |
352 |
| - else { |
353 |
| - // Chunk is not found. |
354 |
| - return Ok((None, chunk_identifier_generator)); |
| 350 | + let chunk_row = match self.chunks.iter().try_fold(0, |mut number_of_chunks, chunk_row| { |
| 351 | + if chunk_row.room_id == room_id { |
| 352 | + number_of_chunks += 1; |
| 353 | + |
| 354 | + if chunk_row.next_chunk.is_none() { |
| 355 | + return ControlFlow::Break((number_of_chunks, chunk_row)); |
| 356 | + } |
| 357 | + } |
| 358 | + |
| 359 | + ControlFlow::Continue(number_of_chunks) |
| 360 | + }) { |
| 361 | + // Chunk has been found, all good. |
| 362 | + ControlFlow::Break((_, chunk_row)) => chunk_row, |
| 363 | + |
| 364 | + // Chunk is not found and there is zero chunk for this room, this is consistent, all |
| 365 | + // good. |
| 366 | + ControlFlow::Continue(0) => { |
| 367 | + return Ok((None, chunk_identifier_generator)); |
| 368 | + } |
| 369 | + |
| 370 | + // Chunk is not found **but** there are chunks for this room, this is inconsistent. The |
| 371 | + // linked chunk is malformed. |
| 372 | + // |
| 373 | + // Returning `Ok(None)` would be invalid here: we must return an error. |
| 374 | + ControlFlow::Continue(_) => { |
| 375 | + return Err( |
| 376 | + "last chunk is not found but chunks exist: the linked chunk contains a cycle" |
| 377 | + .to_owned(), |
| 378 | + ); |
| 379 | + } |
355 | 380 | };
|
356 | 381 |
|
357 | 382 | // Build the chunk.
|
@@ -1188,6 +1213,29 @@ mod tests {
|
1188 | 1213 | }
|
1189 | 1214 | }
|
1190 | 1215 |
|
| 1216 | + #[test] |
| 1217 | + fn test_load_last_chunk_with_a_cycle() { |
| 1218 | + let room_id = room_id!("!r0:matrix.org"); |
| 1219 | + let mut relational_linked_chunk = RelationalLinkedChunk::<char, ()>::new(); |
| 1220 | + |
| 1221 | + relational_linked_chunk.apply_updates( |
| 1222 | + room_id, |
| 1223 | + vec![ |
| 1224 | + Update::NewItemsChunk { previous: None, new: CId::new(0), next: None }, |
| 1225 | + Update::NewItemsChunk { |
| 1226 | + // Because `previous` connects to chunk #0, it will create a cycle. |
| 1227 | + // Chunk #0 will have a `next` set to chunk #1! Consequently, the last chunk |
| 1228 | + // **does not exist**. We have to detect this cycle. |
| 1229 | + previous: Some(CId::new(0)), |
| 1230 | + new: CId::new(1), |
| 1231 | + next: Some(CId::new(0)), |
| 1232 | + }, |
| 1233 | + ], |
| 1234 | + ); |
| 1235 | + |
| 1236 | + relational_linked_chunk.load_last_chunk(room_id).unwrap_err(); |
| 1237 | + } |
| 1238 | + |
1191 | 1239 | #[test]
|
1192 | 1240 | fn test_load_previous_chunk() {
|
1193 | 1241 | let room_id = room_id!("!r0:matrix.org");
|
|
0 commit comments