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