| 
 | 1 | +//! Validator for Signed Document ID  | 
 | 2 | +
  | 
 | 3 | +use std::time::{Duration, SystemTime};  | 
 | 4 | + | 
 | 5 | +use anyhow::Context;  | 
 | 6 | + | 
 | 7 | +use crate::{providers::CatalystSignedDocumentProvider, CatalystSignedDocument};  | 
 | 8 | + | 
 | 9 | +/// Signed Document `id` field validation rule  | 
1 | 10 | pub(crate) struct IdRule;  | 
 | 11 | + | 
 | 12 | +impl IdRule {  | 
 | 13 | +    /// Validates document `id` field on the timestamps:  | 
 | 14 | +    /// 1. If `provider.future_threshold()` not `None`, document `id` cannot be too far in  | 
 | 15 | +    ///    the future (`future_threshold` arg) from `SystemTime::now()` based on the  | 
 | 16 | +    ///    provide threshold  | 
 | 17 | +    /// 2. If `provider.future_threshold()` not `None`, document `id` cannot be too far  | 
 | 18 | +    ///    behind (`past_threshold` arg) from `SystemTime::now()` based on the provide  | 
 | 19 | +    ///    threshold  | 
 | 20 | +    pub(crate) fn check<Provider>(  | 
 | 21 | +        self,  | 
 | 22 | +        doc: &CatalystSignedDocument,  | 
 | 23 | +        provider: &Provider,  | 
 | 24 | +    ) -> anyhow::Result<bool>  | 
 | 25 | +    where  | 
 | 26 | +        Provider: CatalystSignedDocumentProvider,  | 
 | 27 | +    {  | 
 | 28 | +        let Ok(id) = doc.doc_id() else {  | 
 | 29 | +            doc.report().missing_field(  | 
 | 30 | +                "id",  | 
 | 31 | +                "Cannot get the document field during the field validation",  | 
 | 32 | +            );  | 
 | 33 | +            return Ok(false);  | 
 | 34 | +        };  | 
 | 35 | + | 
 | 36 | +        let mut is_valid = true;  | 
 | 37 | + | 
 | 38 | +        let (id_time_secs, id_time_nanos) = id  | 
 | 39 | +            .uuid()  | 
 | 40 | +            .get_timestamp()  | 
 | 41 | +            .ok_or(anyhow::anyhow!("Document id field must be a UUIDv7"))?  | 
 | 42 | +            .to_unix();  | 
 | 43 | + | 
 | 44 | +        let Some(id_time) =  | 
 | 45 | +            SystemTime::UNIX_EPOCH.checked_add(Duration::new(id_time_secs, id_time_nanos))  | 
 | 46 | +        else {  | 
 | 47 | +            doc.report().invalid_value(  | 
 | 48 | +                    "id",  | 
 | 49 | +                    &id.to_string(),  | 
 | 50 | +                    "Must a valid duration since `UNIX_EPOCH`",  | 
 | 51 | +                    "Cannot instantiate a valid `SystemTime` value from the provided `id` field timestamp.",  | 
 | 52 | +                );  | 
 | 53 | +            return Ok(false);  | 
 | 54 | +        };  | 
 | 55 | + | 
 | 56 | +        let now = SystemTime::now();  | 
 | 57 | + | 
 | 58 | +        if let Ok(id_age) = id_time.duration_since(now) {  | 
 | 59 | +            // `now` is earlier than `id_time`  | 
 | 60 | +            if let Some(future_threshold) = provider.future_threshold() {  | 
 | 61 | +                if id_age > future_threshold {  | 
 | 62 | +                    doc.report().invalid_value(  | 
 | 63 | +                        "id",  | 
 | 64 | +                        &id.to_string(),  | 
 | 65 | +                        "id < now + future_threshold",  | 
 | 66 | +                        &format!("Document Version timestamp {id} cannot be too far in future (threshold: {future_threshold:?}) from now: {now:?}"),  | 
 | 67 | +                    );  | 
 | 68 | +                    is_valid = false;  | 
 | 69 | +                }  | 
 | 70 | +            }  | 
 | 71 | +        } else {  | 
 | 72 | +            // `id_time` is earlier than `now`  | 
 | 73 | +            let id_age = now  | 
 | 74 | +                .duration_since(id_time)  | 
 | 75 | +                .context("BUG! `id_time` must be earlier than `now` at this place")?;  | 
 | 76 | + | 
 | 77 | +            if let Some(past_threshold) = provider.past_threshold() {  | 
 | 78 | +                if id_age > past_threshold {  | 
 | 79 | +                    doc.report().invalid_value(  | 
 | 80 | +                        "id",  | 
 | 81 | +                        &id.to_string(),  | 
 | 82 | +                        "id > now - past_threshold",  | 
 | 83 | +                        &format!("Document Version timestamp {id} cannot be too far behind (threshold: {past_threshold:?}) from now: {now:?}",),  | 
 | 84 | +                    );  | 
 | 85 | +                    is_valid = false;  | 
 | 86 | +                }  | 
 | 87 | +            }  | 
 | 88 | +        }  | 
 | 89 | + | 
 | 90 | +        Ok(is_valid)  | 
 | 91 | +    }  | 
 | 92 | +}  | 
0 commit comments