Skip to main content

drive/util/object_size_info/
document_info.rs

1use crate::error::drive::DriveError;
2use crate::error::fee::FeeError;
3use crate::error::Error;
4use crate::util::object_size_info::DriveKeyInfo::{Key, KeySize};
5use crate::util::object_size_info::KeyValueInfo::{KeyRefRequest, KeyValueMaxSize};
6use crate::util::object_size_info::{DriveKeyInfo, KeyValueInfo};
7use crate::util::storage_flags::StorageFlags;
8use crate::util::type_constants::{
9    DEFAULT_HASH_SIZE_U16, DEFAULT_HASH_SIZE_U8, U32_SIZE_U16, U32_SIZE_U8, U64_SIZE_U16,
10    U64_SIZE_U8,
11};
12use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
13use dpp::data_contract::document_type::methods::DocumentTypeBasicMethods;
14use dpp::data_contract::document_type::{DocumentPropertyType, DocumentTypeRef, IndexLevel};
15use dpp::document::document_methods::DocumentMethodsV0;
16use dpp::document::{Document, DocumentV0Getters};
17use dpp::version::PlatformVersion;
18use grovedb::batch::key_info::KeyInfo;
19use std::borrow::Cow;
20
21/// Document info
22#[derive(Clone, Debug)]
23pub enum DocumentInfo<'a> {
24    /// The document without it's serialized form
25    DocumentOwnedInfo((Document, Option<Cow<'a, StorageFlags>>)),
26    /// The borrowed document without it's serialized form
27    DocumentRefInfo((&'a Document, Option<Cow<'a, StorageFlags>>)),
28    /// The borrowed document and it's serialized form
29    DocumentRefAndSerialization((&'a Document, &'a [u8], Option<Cow<'a, StorageFlags>>)),
30    /// The document and it's serialized form
31    DocumentAndSerialization((Document, Vec<u8>, Option<Cow<'a, StorageFlags>>)),
32    /// An element size
33    DocumentEstimatedAverageSize(u32),
34}
35
36/// DocumentInfo V0 Methods
37pub trait DocumentInfoV0Methods {
38    /// Returns true if self is a document with serialization.
39    fn is_document_and_serialization(&self) -> bool;
40    /// Returns true if self is a document size.
41    fn is_document_size(&self) -> bool;
42    /// Gets the borrowed document
43    fn get_borrowed_document(&self) -> Option<&Document>;
44    /// Makes the document ID the key.
45    fn id_key_value_info(&self) -> KeyValueInfo<'_>;
46    /// Gets the raw path for the given document type
47    fn get_estimated_size_for_document_type(
48        &self,
49        key_path: &str,
50        document_type: DocumentTypeRef,
51        platform_version: &PlatformVersion,
52    ) -> Result<u16, Error>;
53    /// Gets the raw path for the given document type
54    fn get_raw_for_document_type(
55        &self,
56        key_path: &str,
57        document_type: DocumentTypeRef,
58        owner_id: Option<[u8; 32]>,
59        size_info_with_base_event: Option<(&IndexLevel, [u8; 32])>,
60        platform_version: &PlatformVersion,
61    ) -> Result<Option<DriveKeyInfo<'_>>, Error>;
62    /// Gets the borrowed document
63    fn get_borrowed_document_and_storage_flags(&self)
64        -> Option<(&Document, Option<&StorageFlags>)>;
65    /// Gets storage flags
66    fn get_storage_flags_ref(&self) -> Option<&StorageFlags>;
67    /// Gets storage flags
68    fn get_document_id_as_slice(&self) -> Option<&[u8]>;
69}
70
71/// The type of the value an index keys `key_path` by: the document type's property at that
72/// path, or the field a derived index property reads on the referenced document (protocol
73/// version 14), which no property of the type carries.
74fn indexed_property_type<'a>(
75    document_type: &'a DocumentTypeRef<'a>,
76    key_path: &str,
77) -> Option<&'a DocumentPropertyType> {
78    match document_type.flattened_properties().get(key_path) {
79        Some(property) => Some(&property.property_type),
80        None => document_type.derived_index_property_type(key_path),
81    }
82}
83
84impl DocumentInfo<'_> {
85    /// The same document without storage flags: how the document of a type declaring a
86    /// `ttl` is written, since such a document refunds nothing. A worst-case size passes
87    /// through unchanged.
88    pub fn without_storage_flags(self) -> Self {
89        match self {
90            DocumentInfo::DocumentOwnedInfo((document, _)) => {
91                DocumentInfo::DocumentOwnedInfo((document, None))
92            }
93            DocumentInfo::DocumentRefInfo((document, _)) => {
94                DocumentInfo::DocumentRefInfo((document, None))
95            }
96            DocumentInfo::DocumentRefAndSerialization((document, serialization, _)) => {
97                DocumentInfo::DocumentRefAndSerialization((document, serialization, None))
98            }
99            DocumentInfo::DocumentAndSerialization((document, serialization, _)) => {
100                DocumentInfo::DocumentAndSerialization((document, serialization, None))
101            }
102            DocumentInfo::DocumentEstimatedAverageSize(size) => {
103                DocumentInfo::DocumentEstimatedAverageSize(size)
104            }
105        }
106    }
107}
108
109impl DocumentInfoV0Methods for DocumentInfo<'_> {
110    /// Returns true if self is a document with serialization.
111    fn is_document_and_serialization(&self) -> bool {
112        matches!(self, DocumentInfo::DocumentRefAndSerialization(..))
113    }
114
115    /// Returns true if self is a document size.
116    fn is_document_size(&self) -> bool {
117        matches!(self, DocumentInfo::DocumentEstimatedAverageSize(_))
118    }
119
120    /// Gets the borrowed document
121    fn get_borrowed_document(&self) -> Option<&Document> {
122        match self {
123            DocumentInfo::DocumentRefAndSerialization((document, _, _))
124            | DocumentInfo::DocumentRefInfo((document, _)) => Some(document),
125            DocumentInfo::DocumentOwnedInfo((document, _))
126            | DocumentInfo::DocumentAndSerialization((document, _, _)) => Some(document),
127            DocumentInfo::DocumentEstimatedAverageSize(_) => None,
128        }
129    }
130
131    /// Makes the document ID the key.
132    fn id_key_value_info(&self) -> KeyValueInfo<'_> {
133        match self {
134            DocumentInfo::DocumentRefAndSerialization((document, _, _))
135            | DocumentInfo::DocumentRefInfo((document, _)) => {
136                KeyRefRequest(document.id_ref().as_slice())
137            }
138            DocumentInfo::DocumentOwnedInfo((document, _))
139            | DocumentInfo::DocumentAndSerialization((document, _, _)) => {
140                KeyRefRequest(document.id_ref().as_slice())
141            }
142            DocumentInfo::DocumentEstimatedAverageSize(document_max_size) => {
143                KeyValueMaxSize((32, *document_max_size))
144            }
145        }
146    }
147
148    /// Gets the raw path for the given document type
149    fn get_estimated_size_for_document_type(
150        &self,
151        key_path: &str,
152        document_type: DocumentTypeRef,
153        platform_version: &PlatformVersion,
154    ) -> Result<u16, Error> {
155        match key_path {
156            "$ownerId" | "$id" | "$creatorId" | "$moderatedBy" => Ok(DEFAULT_HASH_SIZE_U16),
157            "$createdAt" | "$updatedAt" | "$transferredAt" | "$moderatedAt" => Ok(U64_SIZE_U16),
158            "$createdAtBlockHeight" | "$updatedAtBlockHeight" | "$transferredAtBlockHeight" => {
159                Ok(U64_SIZE_U16)
160            }
161            "$createdAtCoreBlockHeight"
162            | "$updatedAtCoreBlockHeight"
163            | "$transferredAtCoreBlockHeight" => Ok(U32_SIZE_U16),
164            key_path => {
165                let property_type = indexed_property_type(&document_type, key_path).ok_or({
166                    Error::Fee(FeeError::DocumentTypeFieldNotFoundForEstimation(format!(
167                        "incorrect key path [{}] for document type for estimated sizes",
168                        key_path
169                    )))
170                })?;
171                let estimated_size = property_type
172                    .middle_byte_size_ceil(platform_version)?
173                    .ok_or({
174                        Error::Drive(DriveError::CorruptedCodeExecution(
175                            "document type must have a max size",
176                        ))
177                    })?;
178                Ok(estimated_size)
179            }
180        }
181    }
182
183    /// Gets the raw path for the given document type
184    fn get_raw_for_document_type(
185        &self,
186        key_path: &str,
187        document_type: DocumentTypeRef,
188        owner_id: Option<[u8; 32]>,
189        size_info_with_base_event: Option<(&IndexLevel, [u8; 32])>,
190        platform_version: &PlatformVersion,
191    ) -> Result<Option<DriveKeyInfo<'_>>, Error> {
192        match self {
193            DocumentInfo::DocumentRefAndSerialization((document, _, _))
194            | DocumentInfo::DocumentRefInfo((document, _)) => {
195                let raw_value = document.get_raw_for_document_type(
196                    key_path,
197                    document_type,
198                    owner_id,
199                    platform_version,
200                )?;
201                match raw_value {
202                    None => Ok(None),
203                    Some(value) => Ok(Some(Key(value))),
204                }
205            }
206            DocumentInfo::DocumentOwnedInfo((document, _))
207            | DocumentInfo::DocumentAndSerialization((document, _, _)) => {
208                let raw_value = document.get_raw_for_document_type(
209                    key_path,
210                    document_type,
211                    owner_id,
212                    platform_version,
213                )?;
214                match raw_value {
215                    None => Ok(None),
216                    Some(value) => Ok(Some(Key(value))),
217                }
218            }
219            DocumentInfo::DocumentEstimatedAverageSize(_) => {
220                let (index_level, base_event) = size_info_with_base_event.ok_or(Error::Drive(
221                    DriveError::CorruptedCodeExecution("size_info_with_base_event None but needed"),
222                ))?;
223                match key_path {
224                    "$ownerId" | "$id" | "$creatorId" | "$moderatedBy" => {
225                        Ok(Some(KeySize(KeyInfo::MaxKeySize {
226                            unique_id: document_type
227                                .unique_id_for_document_field(index_level, base_event)
228                                .to_vec(),
229                            max_size: DEFAULT_HASH_SIZE_U8,
230                        })))
231                    }
232                    "$createdAt" | "$updatedAt" | "$transferredAt" | "$moderatedAt" => {
233                        Ok(Some(KeySize(KeyInfo::MaxKeySize {
234                            unique_id: document_type
235                                .unique_id_for_document_field(index_level, base_event)
236                                .to_vec(),
237                            max_size: U64_SIZE_U8,
238                        })))
239                    }
240                    "$createdAtBlockHeight"
241                    | "$updatedAtBlockHeight"
242                    | "$transferredAtBlockHeight" => Ok(Some(KeySize(KeyInfo::MaxKeySize {
243                        unique_id: document_type
244                            .unique_id_for_document_field(index_level, base_event)
245                            .to_vec(),
246                        max_size: U64_SIZE_U8,
247                    }))),
248                    "$createdAtCoreBlockHeight"
249                    | "$updatedAtCoreBlockHeight"
250                    | "$transferredAtCoreBlockHeight" => Ok(Some(KeySize(KeyInfo::MaxKeySize {
251                        unique_id: document_type
252                            .unique_id_for_document_field(index_level, base_event)
253                            .to_vec(),
254                        max_size: U32_SIZE_U8,
255                    }))),
256                    key_path => {
257                        let property_type = indexed_property_type(&document_type, key_path)
258                            .ok_or({
259                                Error::Fee(FeeError::DocumentTypeFieldNotFoundForEstimation(
260                                    format!("incorrect key path [{}] for document type for get_raw_for_document_type", key_path)
261                                ))
262                            })?;
263
264                        let estimated_middle_size = property_type
265                            .middle_byte_size_ceil(platform_version)?
266                            .ok_or({
267                                Error::Drive(DriveError::CorruptedCodeExecution(
268                                    "document type must have a max size",
269                                ))
270                            })?;
271                        if estimated_middle_size > u8::MAX as u16 {
272                            // this is too big for a key
273                            return Err(Error::Drive(DriveError::CorruptedCodeExecution(
274                                "estimated middle size is too big for a key",
275                            )));
276                        }
277                        Ok(Some(KeySize(KeyInfo::MaxKeySize {
278                            unique_id: document_type
279                                .unique_id_for_document_field(index_level, base_event)
280                                .to_vec(),
281                            max_size: estimated_middle_size as u8,
282                        })))
283                    }
284                }
285            }
286        }
287    }
288
289    /// Gets the borrowed document
290    fn get_borrowed_document_and_storage_flags(
291        &self,
292    ) -> Option<(&Document, Option<&StorageFlags>)> {
293        match self {
294            DocumentInfo::DocumentRefAndSerialization((document, _, storage_flags))
295            | DocumentInfo::DocumentRefInfo((document, storage_flags)) => {
296                Some((document, storage_flags.as_ref().map(|flags| flags.as_ref())))
297            }
298            DocumentInfo::DocumentOwnedInfo((document, storage_flags))
299            | DocumentInfo::DocumentAndSerialization((document, _, storage_flags)) => {
300                Some((document, storage_flags.as_ref().map(|flags| flags.as_ref())))
301            }
302            DocumentInfo::DocumentEstimatedAverageSize(_) => None,
303        }
304    }
305
306    /// Gets storage flags
307    fn get_storage_flags_ref(&self) -> Option<&StorageFlags> {
308        match self {
309            DocumentInfo::DocumentRefAndSerialization((_, _, storage_flags))
310            | DocumentInfo::DocumentRefInfo((_, storage_flags))
311            | DocumentInfo::DocumentOwnedInfo((_, storage_flags))
312            | DocumentInfo::DocumentAndSerialization((_, _, storage_flags)) => {
313                storage_flags.as_ref().map(|flags| flags.as_ref())
314            }
315            DocumentInfo::DocumentEstimatedAverageSize(_) => {
316                StorageFlags::optional_default_as_ref()
317            }
318        }
319    }
320
321    /// Gets storage flags
322    fn get_document_id_as_slice(&self) -> Option<&[u8]> {
323        match self {
324            DocumentInfo::DocumentRefAndSerialization((document, _, _))
325            | DocumentInfo::DocumentRefInfo((document, _)) => Some(document.id_ref().as_slice()),
326            DocumentInfo::DocumentOwnedInfo((document, _))
327            | DocumentInfo::DocumentAndSerialization((document, _, _)) => {
328                Some(document.id_ref().as_slice())
329            }
330            DocumentInfo::DocumentEstimatedAverageSize(_) => None,
331        }
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338    use dpp::document::DocumentV0;
339    use dpp::prelude::Identifier;
340    use std::collections::BTreeMap;
341
342    /// Helper: build a minimal Document (V0) with a given 32-byte id.
343    fn make_document(id_bytes: [u8; 32]) -> Document {
344        Document::V0(DocumentV0 {
345            contract_version: None,
346            id: Identifier::new(id_bytes),
347            owner_id: Identifier::new([0xAA; 32]),
348            properties: BTreeMap::new(),
349            revision: Some(1),
350            created_at: None,
351            updated_at: None,
352            transferred_at: None,
353            created_at_block_height: None,
354            updated_at_block_height: None,
355            transferred_at_block_height: None,
356            created_at_core_block_height: None,
357            updated_at_core_block_height: None,
358            transferred_at_core_block_height: None,
359            creator_id: None,
360            moderated_at: None,
361            moderated_by: None,
362        })
363    }
364
365    // ---------------------------------------------------------------
366    // is_document_and_serialization
367    // ---------------------------------------------------------------
368
369    #[test]
370    fn test_is_document_and_serialization_true_for_ref_and_serialization() {
371        let doc = make_document([1; 32]);
372        let serialized = vec![1, 2, 3];
373        let info = DocumentInfo::DocumentRefAndSerialization((&doc, &serialized, None));
374        assert!(info.is_document_and_serialization());
375    }
376
377    #[test]
378    fn test_is_document_and_serialization_false_for_owned_info() {
379        let doc = make_document([2; 32]);
380        let info = DocumentInfo::DocumentOwnedInfo((doc, None));
381        assert!(!info.is_document_and_serialization());
382    }
383
384    #[test]
385    fn test_is_document_and_serialization_false_for_ref_info() {
386        let doc = make_document([3; 32]);
387        let info = DocumentInfo::DocumentRefInfo((&doc, None));
388        assert!(!info.is_document_and_serialization());
389    }
390
391    #[test]
392    fn test_is_document_and_serialization_false_for_estimated_size() {
393        let info = DocumentInfo::DocumentEstimatedAverageSize(100);
394        assert!(!info.is_document_and_serialization());
395    }
396
397    #[test]
398    fn test_is_document_and_serialization_false_for_document_and_serialization() {
399        let doc = make_document([4; 32]);
400        let info = DocumentInfo::DocumentAndSerialization((doc, vec![9, 8, 7], None));
401        assert!(!info.is_document_and_serialization());
402    }
403
404    // ---------------------------------------------------------------
405    // is_document_size
406    // ---------------------------------------------------------------
407
408    #[test]
409    fn test_is_document_size_true_for_estimated() {
410        let info = DocumentInfo::DocumentEstimatedAverageSize(256);
411        assert!(info.is_document_size());
412    }
413
414    #[test]
415    fn test_is_document_size_false_for_owned_info() {
416        let doc = make_document([5; 32]);
417        let info = DocumentInfo::DocumentOwnedInfo((doc, None));
418        assert!(!info.is_document_size());
419    }
420
421    #[test]
422    fn test_is_document_size_false_for_ref_info() {
423        let doc = make_document([6; 32]);
424        let info = DocumentInfo::DocumentRefInfo((&doc, None));
425        assert!(!info.is_document_size());
426    }
427
428    // ---------------------------------------------------------------
429    // get_borrowed_document
430    // ---------------------------------------------------------------
431
432    #[test]
433    fn test_get_borrowed_document_from_ref_info() {
434        let doc = make_document([10; 32]);
435        let info = DocumentInfo::DocumentRefInfo((&doc, None));
436        let borrowed = info.get_borrowed_document();
437        assert!(borrowed.is_some());
438        assert_eq!(borrowed.unwrap().id_ref().as_slice(), &[10u8; 32]);
439    }
440
441    #[test]
442    fn test_get_borrowed_document_from_ref_and_serialization() {
443        let doc = make_document([11; 32]);
444        let ser = vec![0u8; 5];
445        let info = DocumentInfo::DocumentRefAndSerialization((&doc, &ser, None));
446        let borrowed = info.get_borrowed_document();
447        assert!(borrowed.is_some());
448        assert_eq!(borrowed.unwrap().id_ref().as_slice(), &[11u8; 32]);
449    }
450
451    #[test]
452    fn test_get_borrowed_document_from_owned_info() {
453        let doc = make_document([12; 32]);
454        let info = DocumentInfo::DocumentOwnedInfo((doc, None));
455        let borrowed = info.get_borrowed_document();
456        assert!(borrowed.is_some());
457        assert_eq!(borrowed.unwrap().id_ref().as_slice(), &[12u8; 32]);
458    }
459
460    #[test]
461    fn test_get_borrowed_document_from_document_and_serialization() {
462        let doc = make_document([13; 32]);
463        let info = DocumentInfo::DocumentAndSerialization((doc, vec![1, 2], None));
464        let borrowed = info.get_borrowed_document();
465        assert!(borrowed.is_some());
466        assert_eq!(borrowed.unwrap().id_ref().as_slice(), &[13u8; 32]);
467    }
468
469    #[test]
470    fn test_get_borrowed_document_none_for_estimated() {
471        let info = DocumentInfo::DocumentEstimatedAverageSize(500);
472        assert!(info.get_borrowed_document().is_none());
473    }
474
475    // ---------------------------------------------------------------
476    // id_key_value_info
477    // ---------------------------------------------------------------
478
479    #[test]
480    fn test_id_key_value_info_ref_info_returns_key_ref_request() {
481        let doc = make_document([20; 32]);
482        let info = DocumentInfo::DocumentRefInfo((&doc, None));
483        match info.id_key_value_info() {
484            KeyRefRequest(key) => {
485                assert_eq!(key, &[20u8; 32]);
486            }
487            _ => panic!("expected KeyRefRequest"),
488        }
489    }
490
491    #[test]
492    fn test_id_key_value_info_owned_info_returns_key_ref_request() {
493        let doc = make_document([21; 32]);
494        let info = DocumentInfo::DocumentOwnedInfo((doc, None));
495        match info.id_key_value_info() {
496            KeyRefRequest(key) => {
497                assert_eq!(key, &[21u8; 32]);
498            }
499            _ => panic!("expected KeyRefRequest"),
500        }
501    }
502
503    #[test]
504    fn test_id_key_value_info_estimated_returns_key_value_max_size() {
505        let info = DocumentInfo::DocumentEstimatedAverageSize(999);
506        match info.id_key_value_info() {
507            KeyValueMaxSize((key_size, doc_size)) => {
508                assert_eq!(key_size, 32);
509                assert_eq!(doc_size, 999);
510            }
511            _ => panic!("expected KeyValueMaxSize"),
512        }
513    }
514
515    #[test]
516    fn test_id_key_value_info_ref_and_serialization_returns_key_ref_request() {
517        let doc = make_document([22; 32]);
518        let ser = vec![0u8; 3];
519        let info = DocumentInfo::DocumentRefAndSerialization((&doc, &ser, None));
520        match info.id_key_value_info() {
521            KeyRefRequest(key) => {
522                assert_eq!(key, &[22u8; 32]);
523            }
524            _ => panic!("expected KeyRefRequest"),
525        }
526    }
527
528    #[test]
529    fn test_id_key_value_info_document_and_serialization_returns_key_ref_request() {
530        let doc = make_document([23; 32]);
531        let info = DocumentInfo::DocumentAndSerialization((doc, vec![5, 6, 7], None));
532        match info.id_key_value_info() {
533            KeyRefRequest(key) => {
534                assert_eq!(key, &[23u8; 32]);
535            }
536            _ => panic!("expected KeyRefRequest"),
537        }
538    }
539
540    // ---------------------------------------------------------------
541    // get_estimated_size_for_document_type (system fields)
542    // ---------------------------------------------------------------
543
544    #[test]
545    fn test_estimated_size_for_owner_id() {
546        let info = DocumentInfo::DocumentEstimatedAverageSize(100);
547        // We cannot build a real DocumentTypeRef without a full contract,
548        // but for system fields the document type is not consulted.
549        // The implementation matches on the string key_path first.
550        // We use a "dummy" DocumentTypeRef -- however, DocumentTypeRef requires real data.
551        // Instead, let's verify the system field sizes returned by the function
552        // by checking the match arms directly. Since we can't create a
553        // DocumentTypeRef trivially, we verify the returned sizes are correct
554        // by calling get_estimated_size_for_document_type with a system field.
555        // Unfortunately, DocumentTypeRef is a reference to a real document type,
556        // so we can only test the specific match arms for system fields in a
557        // limited way without creating an entire DataContract. We will
558        // exercise those constant-return paths indirectly through other tests
559        // or verify the constants themselves.
560        //
561        // For now, verify the constants these arms return:
562        assert_eq!(DEFAULT_HASH_SIZE_U16, 32);
563        assert_eq!(U64_SIZE_U16, 8);
564        assert_eq!(U32_SIZE_U16, 4);
565        // These are the values returned for $ownerId/$id, $createdAt/$updatedAt,
566        // and $createdAtCoreBlockHeight etc. respectively.
567        drop(info);
568    }
569
570    // ---------------------------------------------------------------
571    // get_borrowed_document_and_storage_flags
572    // ---------------------------------------------------------------
573
574    #[test]
575    fn test_get_borrowed_document_and_storage_flags_from_ref_info_no_flags() {
576        let doc = make_document([30; 32]);
577        let info = DocumentInfo::DocumentRefInfo((&doc, None));
578        let result = info.get_borrowed_document_and_storage_flags();
579        assert!(result.is_some());
580        let (d, flags) = result.unwrap();
581        assert_eq!(d.id_ref().as_slice(), &[30u8; 32]);
582        assert!(flags.is_none());
583    }
584
585    #[test]
586    fn test_get_borrowed_document_and_storage_flags_from_owned_info_no_flags() {
587        let doc = make_document([31; 32]);
588        let info = DocumentInfo::DocumentOwnedInfo((doc, None));
589        let result = info.get_borrowed_document_and_storage_flags();
590        assert!(result.is_some());
591        let (d, flags) = result.unwrap();
592        assert_eq!(d.id_ref().as_slice(), &[31u8; 32]);
593        assert!(flags.is_none());
594    }
595
596    #[test]
597    fn test_get_borrowed_document_and_storage_flags_none_for_estimated() {
598        let info = DocumentInfo::DocumentEstimatedAverageSize(200);
599        assert!(info.get_borrowed_document_and_storage_flags().is_none());
600    }
601
602    #[test]
603    fn test_get_borrowed_document_and_storage_flags_ref_and_serialization() {
604        let doc = make_document([32; 32]);
605        let ser = vec![7u8; 4];
606        let info = DocumentInfo::DocumentRefAndSerialization((&doc, &ser, None));
607        let result = info.get_borrowed_document_and_storage_flags();
608        assert!(result.is_some());
609        let (d, flags) = result.unwrap();
610        assert_eq!(d.id_ref().as_slice(), &[32u8; 32]);
611        assert!(flags.is_none());
612    }
613
614    #[test]
615    fn test_get_borrowed_document_and_storage_flags_document_and_serialization() {
616        let doc = make_document([33; 32]);
617        let info = DocumentInfo::DocumentAndSerialization((doc, vec![10, 20], None));
618        let result = info.get_borrowed_document_and_storage_flags();
619        assert!(result.is_some());
620        let (d, flags) = result.unwrap();
621        assert_eq!(d.id_ref().as_slice(), &[33u8; 32]);
622        assert!(flags.is_none());
623    }
624
625    // ---------------------------------------------------------------
626    // get_storage_flags_ref
627    // ---------------------------------------------------------------
628
629    #[test]
630    fn test_get_storage_flags_ref_none_without_flags() {
631        let doc = make_document([40; 32]);
632        let info = DocumentInfo::DocumentRefInfo((&doc, None));
633        assert!(info.get_storage_flags_ref().is_none());
634    }
635
636    #[test]
637    fn test_get_storage_flags_ref_none_for_owned_without_flags() {
638        let doc = make_document([41; 32]);
639        let info = DocumentInfo::DocumentOwnedInfo((doc, None));
640        assert!(info.get_storage_flags_ref().is_none());
641    }
642
643    // ---------------------------------------------------------------
644    // get_document_id_as_slice
645    // ---------------------------------------------------------------
646
647    #[test]
648    fn test_get_document_id_as_slice_from_ref_info() {
649        let doc = make_document([50; 32]);
650        let info = DocumentInfo::DocumentRefInfo((&doc, None));
651        assert_eq!(info.get_document_id_as_slice(), Some([50u8; 32].as_slice()));
652    }
653
654    #[test]
655    fn test_get_document_id_as_slice_from_owned_info() {
656        let doc = make_document([51; 32]);
657        let info = DocumentInfo::DocumentOwnedInfo((doc, None));
658        assert_eq!(info.get_document_id_as_slice(), Some([51u8; 32].as_slice()));
659    }
660
661    #[test]
662    fn test_get_document_id_as_slice_from_ref_and_serialization() {
663        let doc = make_document([52; 32]);
664        let ser = vec![0u8; 2];
665        let info = DocumentInfo::DocumentRefAndSerialization((&doc, &ser, None));
666        assert_eq!(info.get_document_id_as_slice(), Some([52u8; 32].as_slice()));
667    }
668
669    #[test]
670    fn test_get_document_id_as_slice_from_document_and_serialization() {
671        let doc = make_document([53; 32]);
672        let info = DocumentInfo::DocumentAndSerialization((doc, vec![3, 4], None));
673        assert_eq!(info.get_document_id_as_slice(), Some([53u8; 32].as_slice()));
674    }
675
676    #[test]
677    fn test_get_document_id_as_slice_none_for_estimated() {
678        let info = DocumentInfo::DocumentEstimatedAverageSize(100);
679        assert!(info.get_document_id_as_slice().is_none());
680    }
681
682    // ---------------------------------------------------------------
683    // Clone behavior
684    // ---------------------------------------------------------------
685
686    #[test]
687    fn test_estimated_average_size_clone_preserves_value() {
688        let info = DocumentInfo::DocumentEstimatedAverageSize(42);
689        let cloned = info.clone();
690        match cloned {
691            DocumentInfo::DocumentEstimatedAverageSize(v) => assert_eq!(v, 42),
692            _ => panic!("clone should preserve variant"),
693        }
694    }
695}