s3sync/pipeline/filter/
modified.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use aws_sdk_s3::primitives::DateTime;
4use aws_smithy_types_convert::date_time::DateTimeExt;
5use tracing::debug;
6
7use crate::config::FilterConfig;
8use crate::pipeline::filter::{ObjectFilter, ObjectFilterBase};
9use crate::pipeline::stage::Stage;
10use crate::storage::e_tag_verify::normalize_e_tag;
11use crate::types::{ObjectKey, ObjectKeyMap, S3syncObject, sha1_digest_from_key};
12
13pub struct TargetModifiedFilter<'a> {
14    base: ObjectFilterBase<'a>,
15}
16
17const FILTER_NAME: &str = "TargetModifiedFilter";
18
19impl TargetModifiedFilter<'_> {
20    pub fn new(base: Stage, target_key_map: Option<ObjectKeyMap>) -> Self {
21        Self {
22            base: ObjectFilterBase {
23                base,
24                target_key_map,
25                name: FILTER_NAME,
26            },
27        }
28    }
29}
30
31#[async_trait]
32impl ObjectFilter for TargetModifiedFilter<'_> {
33    async fn filter(&self) -> Result<()> {
34        if self.base.base.config.filter_config.check_size {
35            self.base.filter(is_modified_from_size).await
36        } else if self.base.base.config.filter_config.check_etag
37            && !self.base.base.config.transfer_config.auto_chunksize
38        {
39            self.base.filter(is_modified_from_e_tag).await
40        } else if self
41            .base
42            .base
43            .config
44            .filter_config
45            .check_checksum_algorithm
46            .is_some()
47            || (self.base.base.config.filter_config.check_etag
48                && self.base.base.config.transfer_config.auto_chunksize)
49        {
50            // check etag will be done within the head object checker
51            self.base.filter(always_modified).await
52        } else {
53            self.base.filter(is_modified_from_timestamp).await
54        }
55    }
56}
57
58fn is_modified_from_timestamp(
59    object: &S3syncObject,
60    _: &FilterConfig,
61    target_key_map: &ObjectKeyMap,
62) -> bool {
63    let target_key_map_map = target_key_map.lock().unwrap();
64    let key = object.key();
65    let source_last_modified_date = object.last_modified();
66
67    let result = target_key_map_map.get(&ObjectKey::KeySHA1Digest(sha1_digest_from_key(key)));
68    if let Some(entry) = result {
69        return filter_last_modified(key, source_last_modified_date, &entry.last_modified);
70    }
71
72    let result = target_key_map_map.get(&ObjectKey::KeyString(key.to_string()));
73    if let Some(entry) = result {
74        return filter_last_modified(key, source_last_modified_date, &entry.last_modified);
75    }
76
77    true
78}
79
80fn filter_last_modified(
81    key: &str,
82    source_last_modified_date: &DateTime,
83    target_last_modified_date: &DateTime,
84) -> bool {
85    let modified =
86        is_source_last_modified_date_newer(source_last_modified_date, target_last_modified_date);
87    if !modified {
88        let source_last_modified =
89            DateTime::from_millis(source_last_modified_date.to_millis().unwrap())
90                .to_chrono_utc()
91                .unwrap()
92                .to_rfc3339();
93        let target_last_modified =
94            DateTime::from_millis(target_last_modified_date.to_millis().unwrap())
95                .to_chrono_utc()
96                .unwrap()
97                .to_rfc3339();
98
99        debug!(
100            name = FILTER_NAME,
101            source_last_modified = source_last_modified,
102            target_last_modified = target_last_modified,
103            key = key,
104            "object filtered."
105        );
106    }
107
108    modified
109}
110
111fn is_source_last_modified_date_newer(
112    source_last_modified_date: &DateTime,
113    target_last_modified_date: &DateTime,
114) -> bool {
115    // GetObjectOutput doesn't have nanos
116    target_last_modified_date.secs() < source_last_modified_date.secs()
117}
118
119fn is_modified_from_size(
120    object: &S3syncObject,
121    _: &FilterConfig,
122    target_key_map: &ObjectKeyMap,
123) -> bool {
124    let target_key_map_map = target_key_map.lock().unwrap();
125    let key = object.key();
126
127    let result = target_key_map_map.get(&ObjectKey::KeySHA1Digest(sha1_digest_from_key(key)));
128    if let Some(entry) = result {
129        let modified = entry.content_length != object.size();
130        if !modified {
131            debug!(
132                name = FILTER_NAME,
133                content_length = entry.content_length,
134                key = key,
135                "object filtered(same size)."
136            );
137        }
138        return modified;
139    }
140
141    let result = target_key_map_map.get(&ObjectKey::KeyString(key.to_string()));
142    if let Some(entry) = result {
143        let modified = entry.content_length != object.size();
144        if !modified {
145            debug!(
146                name = FILTER_NAME,
147                content_length = entry.content_length,
148                key = key,
149                "object filtered(same size)."
150            );
151        }
152        return modified;
153    }
154
155    true
156}
157
158fn is_modified_from_e_tag(
159    object: &S3syncObject,
160    _: &FilterConfig,
161    target_key_map: &ObjectKeyMap,
162) -> bool {
163    let locked_target_key_map = target_key_map.lock().unwrap();
164    let key = object.key();
165
166    let mut result =
167        locked_target_key_map.get(&ObjectKey::KeySHA1Digest(sha1_digest_from_key(key)));
168    if result.is_none() {
169        result = locked_target_key_map.get(&ObjectKey::KeyString(key.to_string()));
170    }
171
172    if let Some(entry) = result {
173        let source_e_tag = normalize_e_tag(&Some(object.e_tag().unwrap().to_string()));
174        let target_e_tag = normalize_e_tag(&Some(entry.e_tag.clone().unwrap()));
175
176        let source_last_modified =
177            DateTime::from_millis(object.last_modified().to_millis().unwrap())
178                .to_chrono_utc()
179                .unwrap()
180                .to_rfc3339();
181        let target_last_modified = DateTime::from_millis(entry.last_modified.to_millis().unwrap())
182            .to_chrono_utc()
183            .unwrap()
184            .to_rfc3339();
185
186        if source_e_tag == target_e_tag {
187            debug!(
188                name = FILTER_NAME,
189                source_e_tag = source_e_tag,
190                target_e_tag = target_e_tag,
191                source_last_modified = source_last_modified,
192                target_last_modified = target_last_modified,
193                source_size = object.size(),
194                target_size = entry.content_length,
195                key = key,
196                "object filtered. ETags are the same."
197            );
198            return false;
199        } else {
200            debug!(
201                name = FILTER_NAME,
202                source_e_tag = source_e_tag,
203                target_e_tag = target_e_tag,
204                source_last_modified = source_last_modified,
205                target_last_modified = target_last_modified,
206                source_size = object.size(),
207                target_size = entry.content_length,
208                key = key,
209                "ETags are different."
210            );
211        }
212
213        return true;
214    }
215
216    true
217}
218
219fn always_modified(_: &S3syncObject, _: &FilterConfig, _: &ObjectKeyMap) -> bool {
220    true
221}
222
223#[cfg(test)]
224mod tests {
225    use std::collections::HashMap;
226    use std::sync::Mutex;
227
228    use super::*;
229    use crate::callback::filter_manager::FilterManager;
230    use crate::config::FilterConfig;
231    use crate::types::{ObjectEntry, S3syncObject};
232    use aws_sdk_s3::primitives::DateTime;
233    use aws_sdk_s3::types::Object;
234    use tracing_subscriber::EnvFilter;
235
236    #[tokio::test]
237    async fn not_modified_sha1() {
238        init_dummy_tracing_subscriber();
239
240        let object = S3syncObject::NotVersioning(
241            Object::builder()
242                .key("test")
243                .last_modified(DateTime::from_secs(1))
244                .build(),
245        );
246
247        let config = FilterConfig {
248            before_time: None,
249            after_time: None,
250            remove_modified_filter: false,
251            check_size: false,
252            check_etag: false,
253            check_mtime_and_etag: false,
254            check_checksum_algorithm: None,
255            check_mtime_and_additional_checksum: None,
256            include_regex: None,
257            exclude_regex: None,
258            include_content_type_regex: None,
259            exclude_content_type_regex: None,
260            include_metadata_regex: None,
261            exclude_metadata_regex: None,
262            include_tag_regex: None,
263            exclude_tag_regex: None,
264            larger_size: None,
265            smaller_size: None,
266            filter_manager: FilterManager::new(),
267        };
268
269        assert!(is_modified_from_timestamp(
270            &object,
271            &config,
272            &ObjectKeyMap::new(Mutex::new(HashMap::new()))
273        ));
274    }
275
276    #[tokio::test]
277    async fn modified_sha1() {
278        init_dummy_tracing_subscriber();
279
280        let object = S3syncObject::NotVersioning(
281            Object::builder()
282                .key("test")
283                .last_modified(DateTime::from_secs(1))
284                .build(),
285        );
286
287        let config = FilterConfig {
288            before_time: None,
289            after_time: None,
290            remove_modified_filter: false,
291            check_size: false,
292            check_etag: false,
293            check_mtime_and_etag: false,
294            check_checksum_algorithm: None,
295            check_mtime_and_additional_checksum: None,
296            include_regex: None,
297            exclude_regex: None,
298            include_content_type_regex: None,
299            exclude_content_type_regex: None,
300            include_metadata_regex: None,
301            exclude_metadata_regex: None,
302            include_tag_regex: None,
303            exclude_tag_regex: None,
304            larger_size: None,
305            smaller_size: None,
306            filter_manager: FilterManager::new(),
307        };
308
309        let mut key_map = HashMap::new();
310        key_map.insert(
311            ObjectKey::KeySHA1Digest(sha1_digest_from_key("test")),
312            ObjectEntry {
313                last_modified: DateTime::from_secs(1),
314                content_length: 1,
315                e_tag: None,
316            },
317        );
318
319        assert!(!is_modified_from_timestamp(
320            &object,
321            &config,
322            &ObjectKeyMap::new(Mutex::new(key_map))
323        ));
324    }
325
326    #[tokio::test]
327    async fn is_modified_true() {
328        init_dummy_tracing_subscriber();
329
330        let object = S3syncObject::NotVersioning(
331            Object::builder()
332                .key("test")
333                .last_modified(DateTime::from_secs(1))
334                .build(),
335        );
336
337        let config = FilterConfig {
338            before_time: None,
339            after_time: None,
340            remove_modified_filter: false,
341            check_size: false,
342            check_etag: false,
343            check_mtime_and_etag: false,
344            check_checksum_algorithm: None,
345            check_mtime_and_additional_checksum: None,
346            include_regex: None,
347            exclude_regex: None,
348            include_metadata_regex: None,
349            exclude_metadata_regex: None,
350            include_content_type_regex: None,
351            exclude_content_type_regex: None,
352            include_tag_regex: None,
353            exclude_tag_regex: None,
354            larger_size: None,
355            smaller_size: None,
356            filter_manager: FilterManager::new(),
357        };
358
359        assert!(is_modified_from_timestamp(
360            &object,
361            &config,
362            &ObjectKeyMap::new(Mutex::new(HashMap::new()))
363        ));
364    }
365
366    #[tokio::test]
367    async fn is_modified_false() {
368        init_dummy_tracing_subscriber();
369
370        let object = S3syncObject::NotVersioning(
371            Object::builder()
372                .key("test")
373                .last_modified(DateTime::from_secs(1))
374                .build(),
375        );
376
377        let config = FilterConfig {
378            before_time: None,
379            after_time: None,
380            remove_modified_filter: false,
381            check_size: false,
382            check_etag: false,
383            check_mtime_and_etag: false,
384            check_checksum_algorithm: None,
385            check_mtime_and_additional_checksum: None,
386            include_regex: None,
387            exclude_regex: None,
388            include_content_type_regex: None,
389            exclude_content_type_regex: None,
390            include_metadata_regex: None,
391            exclude_metadata_regex: None,
392            include_tag_regex: None,
393            exclude_tag_regex: None,
394            larger_size: None,
395            smaller_size: None,
396            filter_manager: FilterManager::new(),
397        };
398
399        let mut key_map = HashMap::new();
400        key_map.insert(
401            ObjectKey::KeyString("test".to_string()),
402            ObjectEntry {
403                last_modified: DateTime::from_secs(1),
404                content_length: 1,
405                e_tag: None,
406            },
407        );
408
409        assert!(!is_modified_from_timestamp(
410            &object,
411            &config,
412            &ObjectKeyMap::new(Mutex::new(key_map))
413        ));
414    }
415
416    #[test]
417    fn filter_modified_false() {
418        init_dummy_tracing_subscriber();
419
420        assert!(!filter_last_modified(
421            "key",
422            &DateTime::from_secs(0),
423            &DateTime::from_secs(1)
424        ));
425        assert!(!filter_last_modified(
426            "key",
427            &DateTime::from_secs(1),
428            &DateTime::from_secs(1)
429        ));
430    }
431
432    #[test]
433    fn filter_modified_true() {
434        init_dummy_tracing_subscriber();
435
436        assert!(filter_last_modified(
437            "key",
438            &DateTime::from_secs(1),
439            &DateTime::from_secs(0)
440        ));
441    }
442
443    #[tokio::test]
444    async fn size_modified_sha1_empty() {
445        init_dummy_tracing_subscriber();
446
447        let object = S3syncObject::NotVersioning(Object::builder().key("test").size(1).build());
448
449        let config = FilterConfig {
450            before_time: None,
451            after_time: None,
452            remove_modified_filter: false,
453            check_size: true,
454            check_etag: false,
455            check_mtime_and_etag: false,
456            check_checksum_algorithm: None,
457            check_mtime_and_additional_checksum: None,
458            include_regex: None,
459            exclude_regex: None,
460            include_content_type_regex: None,
461            exclude_content_type_regex: None,
462            include_metadata_regex: None,
463            exclude_metadata_regex: None,
464            include_tag_regex: None,
465            exclude_tag_regex: None,
466            larger_size: None,
467            smaller_size: None,
468            filter_manager: FilterManager::new(),
469        };
470
471        assert!(is_modified_from_size(
472            &object,
473            &config,
474            &ObjectKeyMap::new(Mutex::new(HashMap::new()))
475        ));
476    }
477
478    #[tokio::test]
479    async fn size_not_modified_sha1() {
480        init_dummy_tracing_subscriber();
481
482        let object = S3syncObject::NotVersioning(Object::builder().key("test").size(1).build());
483
484        let config = FilterConfig {
485            before_time: None,
486            after_time: None,
487            remove_modified_filter: false,
488            check_size: true,
489            check_etag: false,
490            check_mtime_and_etag: false,
491            check_checksum_algorithm: None,
492            check_mtime_and_additional_checksum: None,
493            include_regex: None,
494            exclude_regex: None,
495            include_content_type_regex: None,
496            exclude_content_type_regex: None,
497            include_metadata_regex: None,
498            exclude_metadata_regex: None,
499            include_tag_regex: None,
500            exclude_tag_regex: None,
501            larger_size: None,
502            smaller_size: None,
503            filter_manager: FilterManager::new(),
504        };
505
506        let mut key_map = HashMap::new();
507        key_map.insert(
508            ObjectKey::KeySHA1Digest(sha1_digest_from_key("test")),
509            ObjectEntry {
510                last_modified: DateTime::from_secs(99),
511                content_length: 1,
512                e_tag: None,
513            },
514        );
515
516        assert!(!is_modified_from_size(
517            &object,
518            &config,
519            &ObjectKeyMap::new(Mutex::new(key_map))
520        ));
521    }
522
523    #[tokio::test]
524    async fn size_modified_sha1() {
525        init_dummy_tracing_subscriber();
526
527        let object = S3syncObject::NotVersioning(Object::builder().key("test").size(1).build());
528
529        let config = FilterConfig {
530            before_time: None,
531            after_time: None,
532            remove_modified_filter: false,
533            check_size: true,
534            check_etag: false,
535            check_mtime_and_etag: false,
536            check_checksum_algorithm: None,
537            check_mtime_and_additional_checksum: None,
538            include_regex: None,
539            exclude_regex: None,
540            include_content_type_regex: None,
541            exclude_content_type_regex: None,
542            include_metadata_regex: None,
543            exclude_metadata_regex: None,
544            include_tag_regex: None,
545            exclude_tag_regex: None,
546            larger_size: None,
547            smaller_size: None,
548            filter_manager: FilterManager::new(),
549        };
550
551        let mut key_map = HashMap::new();
552        key_map.insert(
553            ObjectKey::KeySHA1Digest(sha1_digest_from_key("test")),
554            ObjectEntry {
555                last_modified: DateTime::from_secs(99),
556                content_length: 2,
557                e_tag: None,
558            },
559        );
560
561        assert!(is_modified_from_size(
562            &object,
563            &config,
564            &ObjectKeyMap::new(Mutex::new(key_map))
565        ));
566    }
567
568    #[tokio::test]
569    async fn size_not_modified_key() {
570        init_dummy_tracing_subscriber();
571
572        let object = S3syncObject::NotVersioning(Object::builder().key("test").size(1).build());
573
574        let config = FilterConfig {
575            before_time: None,
576            after_time: None,
577            remove_modified_filter: false,
578            check_size: true,
579            check_etag: false,
580            check_mtime_and_etag: false,
581            check_checksum_algorithm: None,
582            check_mtime_and_additional_checksum: None,
583            include_regex: None,
584            exclude_regex: None,
585            include_content_type_regex: None,
586            exclude_content_type_regex: None,
587            include_metadata_regex: None,
588            exclude_metadata_regex: None,
589            include_tag_regex: None,
590            exclude_tag_regex: None,
591            larger_size: None,
592            smaller_size: None,
593            filter_manager: FilterManager::new(),
594        };
595
596        let mut key_map = HashMap::new();
597        key_map.insert(
598            ObjectKey::KeyString("test".to_string()),
599            ObjectEntry {
600                last_modified: DateTime::from_secs(99),
601                content_length: 1,
602                e_tag: None,
603            },
604        );
605
606        assert!(!is_modified_from_size(
607            &object,
608            &config,
609            &ObjectKeyMap::new(Mutex::new(key_map))
610        ));
611    }
612
613    #[tokio::test]
614    async fn size_modified_key() {
615        init_dummy_tracing_subscriber();
616
617        let object = S3syncObject::NotVersioning(Object::builder().key("test").size(1).build());
618
619        let config = FilterConfig {
620            before_time: None,
621            after_time: None,
622            remove_modified_filter: false,
623            check_size: true,
624            check_etag: false,
625            check_mtime_and_etag: false,
626            check_checksum_algorithm: None,
627            check_mtime_and_additional_checksum: None,
628            include_regex: None,
629            exclude_regex: None,
630            include_content_type_regex: None,
631            exclude_content_type_regex: None,
632            include_metadata_regex: None,
633            exclude_metadata_regex: None,
634            include_tag_regex: None,
635            exclude_tag_regex: None,
636            larger_size: None,
637            smaller_size: None,
638            filter_manager: FilterManager::new(),
639        };
640
641        let mut key_map = HashMap::new();
642        key_map.insert(
643            ObjectKey::KeyString("test".to_string()),
644            ObjectEntry {
645                last_modified: DateTime::from_secs(99),
646                content_length: 2,
647                e_tag: None,
648            },
649        );
650
651        assert!(is_modified_from_size(
652            &object,
653            &config,
654            &ObjectKeyMap::new(Mutex::new(key_map))
655        ));
656    }
657
658    fn init_dummy_tracing_subscriber() {
659        let _ = tracing_subscriber::fmt()
660            .with_env_filter(
661                EnvFilter::try_from_default_env()
662                    .or_else(|_| EnvFilter::try_new("trace"))
663                    .unwrap(),
664            )
665            .try_init();
666    }
667
668    #[tokio::test]
669    async fn size_not_modified_e_tag() {
670        init_dummy_tracing_subscriber();
671
672        let object = S3syncObject::NotVersioning(
673            Object::builder()
674                .key("test")
675                .size(1)
676                .e_tag("0dd7cd23c492b5a3a62672b4049bb1ed")
677                .last_modified(DateTime::from_secs(99))
678                .build(),
679        );
680
681        let config = FilterConfig {
682            before_time: None,
683            after_time: None,
684            remove_modified_filter: false,
685            check_size: false,
686            check_etag: true,
687            check_mtime_and_etag: false,
688            check_checksum_algorithm: None,
689            check_mtime_and_additional_checksum: None,
690            include_regex: None,
691            exclude_regex: None,
692            include_content_type_regex: None,
693            exclude_content_type_regex: None,
694            include_metadata_regex: None,
695            exclude_metadata_regex: None,
696            include_tag_regex: None,
697            exclude_tag_regex: None,
698            larger_size: None,
699            smaller_size: None,
700            filter_manager: FilterManager::new(),
701        };
702
703        let mut key_map = HashMap::new();
704        key_map.insert(
705            ObjectKey::KeySHA1Digest(sha1_digest_from_key("test")),
706            ObjectEntry {
707                last_modified: DateTime::from_secs(99),
708                content_length: 1,
709                e_tag: Some("0dd7cd23c492b5a3a62672b4049bb1ed".to_string()),
710            },
711        );
712
713        assert!(!is_modified_from_e_tag(
714            &object,
715            &config,
716            &ObjectKeyMap::new(Mutex::new(key_map))
717        ));
718    }
719
720    #[tokio::test]
721    async fn size_not_modified_e_tag_normalize_source() {
722        init_dummy_tracing_subscriber();
723
724        let object = S3syncObject::NotVersioning(
725            Object::builder()
726                .key("test")
727                .size(1)
728                .e_tag("\"0dd7cd23c492b5a3a62672b4049bb1ed\"")
729                .last_modified(DateTime::from_secs(99))
730                .build(),
731        );
732
733        let config = FilterConfig {
734            before_time: None,
735            after_time: None,
736            remove_modified_filter: false,
737            check_size: false,
738            check_etag: true,
739            check_mtime_and_etag: false,
740            check_checksum_algorithm: None,
741            check_mtime_and_additional_checksum: None,
742            include_regex: None,
743            exclude_regex: None,
744            include_content_type_regex: None,
745            exclude_content_type_regex: None,
746            include_metadata_regex: None,
747            exclude_metadata_regex: None,
748            include_tag_regex: None,
749            exclude_tag_regex: None,
750            larger_size: None,
751            smaller_size: None,
752            filter_manager: FilterManager::new(),
753        };
754
755        let mut key_map = HashMap::new();
756        key_map.insert(
757            ObjectKey::KeySHA1Digest(sha1_digest_from_key("test")),
758            ObjectEntry {
759                last_modified: DateTime::from_secs(99),
760                content_length: 1,
761                e_tag: Some("0dd7cd23c492b5a3a62672b4049bb1ed".to_string()),
762            },
763        );
764
765        assert!(!is_modified_from_e_tag(
766            &object,
767            &config,
768            &ObjectKeyMap::new(Mutex::new(key_map))
769        ));
770    }
771
772    #[tokio::test]
773    async fn size_not_modified_e_tag_normalize_target() {
774        init_dummy_tracing_subscriber();
775
776        let object = S3syncObject::NotVersioning(
777            Object::builder()
778                .key("test")
779                .size(1)
780                .e_tag("0dd7cd23c492b5a3a62672b4049bb1ed")
781                .last_modified(DateTime::from_secs(99))
782                .build(),
783        );
784
785        let config = FilterConfig {
786            before_time: None,
787            after_time: None,
788            remove_modified_filter: false,
789            check_size: false,
790            check_etag: true,
791            check_mtime_and_etag: false,
792            check_checksum_algorithm: None,
793            check_mtime_and_additional_checksum: None,
794            include_regex: None,
795            exclude_regex: None,
796            include_content_type_regex: None,
797            exclude_content_type_regex: None,
798            include_metadata_regex: None,
799            exclude_metadata_regex: None,
800            include_tag_regex: None,
801            exclude_tag_regex: None,
802            larger_size: None,
803            smaller_size: None,
804            filter_manager: FilterManager::new(),
805        };
806
807        let mut key_map = HashMap::new();
808        key_map.insert(
809            ObjectKey::KeySHA1Digest(sha1_digest_from_key("test")),
810            ObjectEntry {
811                last_modified: DateTime::from_secs(99),
812                content_length: 1,
813                e_tag: Some("\"0dd7cd23c492b5a3a62672b4049bb1ed\"".to_string()),
814            },
815        );
816
817        assert!(!is_modified_from_e_tag(
818            &object,
819            &config,
820            &ObjectKeyMap::new(Mutex::new(key_map))
821        ));
822    }
823
824    #[tokio::test]
825    async fn size_modified_e_tag() {
826        init_dummy_tracing_subscriber();
827
828        let object = S3syncObject::NotVersioning(
829            Object::builder()
830                .key("test")
831                .size(1)
832                .e_tag("add7cd23c492b5a3a62672b4049bb1ed")
833                .last_modified(DateTime::from_secs(99))
834                .build(),
835        );
836
837        let config = FilterConfig {
838            before_time: None,
839            after_time: None,
840            remove_modified_filter: false,
841            check_size: false,
842            check_etag: true,
843            check_mtime_and_etag: false,
844            check_checksum_algorithm: None,
845            check_mtime_and_additional_checksum: None,
846            include_regex: None,
847            exclude_regex: None,
848            include_content_type_regex: None,
849            exclude_content_type_regex: None,
850            include_metadata_regex: None,
851            exclude_metadata_regex: None,
852            include_tag_regex: None,
853            exclude_tag_regex: None,
854            larger_size: None,
855            smaller_size: None,
856            filter_manager: FilterManager::new(),
857        };
858
859        let mut key_map = HashMap::new();
860        key_map.insert(
861            ObjectKey::KeySHA1Digest(sha1_digest_from_key("test")),
862            ObjectEntry {
863                last_modified: DateTime::from_secs(99),
864                content_length: 1,
865                e_tag: Some("0dd7cd23c492b5a3a62672b4049bb1ed".to_string()),
866            },
867        );
868
869        assert!(is_modified_from_e_tag(
870            &object,
871            &config,
872            &ObjectKeyMap::new(Mutex::new(key_map))
873        ));
874    }
875}