1use core::fmt::Display;
8use core::str::FromStr;
9
10#[cfg(feature = "serde")]
11use serde::de::Visitor;
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15use crate::compare::ComparisonConfiguration;
16use crate::errors::{OperationError, ParseError};
17use crate::hash::body::FuzzyHashBody;
18use crate::hash::checksum::FuzzyHashChecksum;
19use crate::hash::qratios::FuzzyHashQRatios;
20use crate::length::FuzzyHashLengthEncoding;
21use crate::params::{ConstrainedFuzzyHashParams, FuzzyHashParams};
22use crate::FuzzyHashType;
23
24pub mod body;
25pub mod checksum;
26pub mod qratios;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
30pub enum HexStringPrefix {
31 Empty,
39
40 #[default]
51 WithVersion,
52}
53
54pub(crate) mod public {
56 use super::*;
57
58 pub trait FuzzyHashType: Sized + FromStr<Err = ParseError> + Display {
83 type ChecksumType: FuzzyHashChecksum;
88
89 type BodyType: FuzzyHashBody;
94
95 const NUMBER_OF_BUCKETS: usize;
113
114 const SIZE_IN_BYTES: usize;
120
121 const LEN_IN_STR_EXCEPT_PREFIX: usize;
130
131 const LEN_IN_STR: usize;
140
141 fn checksum(&self) -> &Self::ChecksumType;
143 fn length(&self) -> &FuzzyHashLengthEncoding;
145 fn qratios(&self) -> &FuzzyHashQRatios;
147 fn body(&self) -> &Self::BodyType;
149
150 fn from_str_bytes(
157 bytes: &[u8],
158 prefix: Option<HexStringPrefix>,
159 ) -> Result<Self, ParseError>;
160
161 #[inline(always)]
168 fn from_str_with(s: &str, prefix: Option<HexStringPrefix>) -> Result<Self, ParseError> {
169 Self::from_str_bytes(s.as_bytes(), prefix)
170 }
171
172 fn store_into_bytes(&self, out: &mut [u8]) -> Result<usize, OperationError>;
241
242 fn store_into_str_bytes(
248 &self,
249 out: &mut [u8],
250 prefix: HexStringPrefix,
251 ) -> Result<usize, OperationError>;
252
253 fn max_distance(config: ComparisonConfiguration) -> u32;
259
260 fn compare_with_config(&self, other: &Self, config: ComparisonConfiguration) -> u32;
266
267 #[inline(always)]
273 fn compare(&self, other: &Self) -> u32 {
274 self.compare_with_config(other, ComparisonConfiguration::Default)
275 }
276 }
277}
278
279pub(crate) mod inner {
281 use super::*;
282
283 use crate::buckets::constrained::{FuzzyHashBucketMapper, FuzzyHashBucketsInfo};
284 use crate::hash::body::FuzzyHashBodyData;
285 use crate::hash::checksum::FuzzyHashChecksumData;
286 use crate::macros::{invariant, optionally_unsafe};
287 use crate::params::{ConstrainedVerboseFuzzyHashParams, VerboseFuzzyHashParams};
288 #[cfg(not(feature = "opt-simd-convert-hex"))]
289 use crate::parse::hex_str::encode_array;
290 use crate::parse::hex_str::{encode_rev_1, encode_rev_array};
291
292 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
296 pub struct FuzzyHash<
297 const SIZE_CKSUM: usize,
298 const SIZE_BODY: usize,
299 const SIZE_BUCKETS: usize,
300 const SIZE_IN_BYTES: usize,
301 const SIZE_IN_STR_BYTES: usize,
302 >
303 where
304 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
305 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
306 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
307 VerboseFuzzyHashParams<
308 SIZE_CKSUM,
309 SIZE_BODY,
310 SIZE_BUCKETS,
311 SIZE_IN_BYTES,
312 SIZE_IN_STR_BYTES,
313 >: ConstrainedVerboseFuzzyHashParams,
314 {
315 body: FuzzyHashBodyData<SIZE_BODY>,
317 checksum: FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>,
319 lvalue: FuzzyHashLengthEncoding,
321 qratios: FuzzyHashQRatios,
323 }
324
325 impl<
326 const SIZE_CKSUM: usize,
327 const SIZE_BODY: usize,
328 const SIZE_BUCKETS: usize,
329 const SIZE_IN_BYTES: usize,
330 const SIZE_IN_STR_BYTES: usize,
331 > FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
332 where
333 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
334 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
335 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
336 VerboseFuzzyHashParams<
337 SIZE_CKSUM,
338 SIZE_BODY,
339 SIZE_BUCKETS,
340 SIZE_IN_BYTES,
341 SIZE_IN_STR_BYTES,
342 >: ConstrainedVerboseFuzzyHashParams,
343 {
344 pub(crate) fn from_raw(
346 body: FuzzyHashBodyData<SIZE_BODY>,
347 checksum: FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>,
348 lvalue: FuzzyHashLengthEncoding,
349 qratios: FuzzyHashQRatios,
350 ) -> Self {
351 Self {
352 body,
353 checksum,
354 lvalue,
355 qratios,
356 }
357 }
358 }
359
360 impl<
361 const SIZE_CKSUM: usize,
362 const SIZE_BODY: usize,
363 const SIZE_BUCKETS: usize,
364 const SIZE_IN_BYTES: usize,
365 const SIZE_IN_STR_BYTES: usize,
366 > FuzzyHashType
367 for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
368 where
369 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
370 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
371 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
372 VerboseFuzzyHashParams<
373 SIZE_CKSUM,
374 SIZE_BODY,
375 SIZE_BUCKETS,
376 SIZE_IN_BYTES,
377 SIZE_IN_STR_BYTES,
378 >: ConstrainedVerboseFuzzyHashParams,
379 {
380 type ChecksumType = FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>;
381 type BodyType = FuzzyHashBodyData<SIZE_BODY>;
382
383 const NUMBER_OF_BUCKETS: usize = SIZE_BUCKETS;
384 const SIZE_IN_BYTES: usize = SIZE_IN_BYTES;
385 const LEN_IN_STR_EXCEPT_PREFIX: usize = SIZE_IN_STR_BYTES - 2;
386 const LEN_IN_STR: usize = SIZE_IN_STR_BYTES;
387
388 #[inline]
389 fn from_str_bytes(
390 bytes: &[u8],
391 prefix: Option<HexStringPrefix>,
392 ) -> Result<Self, ParseError> {
393 let mut bytes = bytes;
394 let prefix = match prefix {
395 None => {
396 if bytes.len() == Self::LEN_IN_STR_EXCEPT_PREFIX {
397 HexStringPrefix::Empty
398 } else if bytes.len() == Self::LEN_IN_STR {
399 HexStringPrefix::WithVersion
400 } else {
401 return Err(ParseError::InvalidStringLength);
402 }
403 }
404 Some(x) => x,
405 };
406 match prefix {
407 HexStringPrefix::Empty => {
408 if bytes.len() != Self::LEN_IN_STR_EXCEPT_PREFIX {
409 return Err(ParseError::InvalidStringLength);
410 }
411 }
412 HexStringPrefix::WithVersion => {
413 if bytes.len() != Self::LEN_IN_STR {
414 return Err(ParseError::InvalidStringLength);
415 }
416 if &bytes[0..2] != b"T1" {
417 return Err(ParseError::InvalidPrefix);
418 }
419 bytes = &bytes[2..];
420 }
421 }
422 let checksum = FuzzyHashChecksumData::<SIZE_CKSUM, SIZE_BUCKETS>::from_str_bytes(
423 &bytes[0..SIZE_CKSUM * 2],
424 )?;
425 #[cfg(feature = "strict-parser")]
426 if !checksum.is_valid() {
427 return Err(ParseError::InvalidChecksum);
428 }
429 bytes = &bytes[SIZE_CKSUM * 2..];
430 let lvalue = FuzzyHashLengthEncoding::from_str_bytes(&bytes[0..2])?;
431 #[cfg(feature = "strict-parser")]
432 if !lvalue.is_valid() {
433 return Err(ParseError::LengthIsTooLarge);
434 }
435 let qratios = FuzzyHashQRatios::from_str_bytes(&bytes[2..4])?;
436 let body = FuzzyHashBodyData::<SIZE_BODY>::from_str_bytes(&bytes[4..])?;
437 Ok(Self {
438 body,
439 checksum,
440 lvalue,
441 qratios,
442 })
443 }
444
445 #[inline(always)]
446 fn checksum(&self) -> &Self::ChecksumType {
447 &self.checksum
448 }
449 #[inline(always)]
450 fn length(&self) -> &FuzzyHashLengthEncoding {
451 &self.lvalue
452 }
453 #[inline(always)]
454 fn qratios(&self) -> &FuzzyHashQRatios {
455 &self.qratios
456 }
457 #[inline(always)]
458 fn body(&self) -> &Self::BodyType {
459 &self.body
460 }
461
462 #[inline]
463 fn store_into_bytes(&self, out: &mut [u8]) -> Result<usize, crate::errors::OperationError> {
464 if out.len() < Self::SIZE_IN_BYTES {
465 return Err(OperationError::BufferIsTooSmall);
466 }
467 out[0..SIZE_CKSUM].copy_from_slice(self.checksum.data());
468 out[SIZE_CKSUM] = self.lvalue.value();
469 out[SIZE_CKSUM + 1] = self.qratios.value();
470 out[SIZE_CKSUM + 2..SIZE_IN_BYTES].copy_from_slice(self.body.data());
471 Ok(Self::SIZE_IN_BYTES)
472 }
473
474 #[inline]
475 fn store_into_str_bytes(
476 &self,
477 out: &mut [u8],
478 prefix: HexStringPrefix,
479 ) -> Result<usize, crate::errors::OperationError> {
480 let len = match prefix {
481 HexStringPrefix::Empty => Self::LEN_IN_STR_EXCEPT_PREFIX,
482 HexStringPrefix::WithVersion => Self::LEN_IN_STR,
483 };
484 if out.len() < len {
485 return Err(OperationError::BufferIsTooSmall);
486 }
487 let mut out = out;
488 if prefix == HexStringPrefix::WithVersion {
489 out[0..2].copy_from_slice(b"T1");
490 out = &mut out[2..];
491 }
492 encode_rev_array(out, self.checksum.data());
493 out = &mut out[SIZE_CKSUM * 2..];
494 encode_rev_1(&mut out[0..2], self.lvalue.value());
495 encode_rev_1(&mut out[2..4], self.qratios.value());
496 cfg_if::cfg_if! {
497 if #[cfg(feature = "opt-simd-convert-hex")] {
498 let _ = hex_simd::encode(
499 self.body.data(),
500 hex_simd::Out::from_slice(&mut out[4..]),
501 hex_simd::AsciiCase::Upper,
502 );
503 } else {
504 encode_array(&mut out[4..], self.body.data());
505 }
506 }
507 Ok(len)
508 }
509
510 #[inline]
511 fn max_distance(config: ComparisonConfiguration) -> u32 {
512 FuzzyHashBodyData::<SIZE_BODY>::MAX_DISTANCE
513 + FuzzyHashChecksumData::<SIZE_CKSUM, SIZE_BUCKETS>::MAX_DISTANCE
514 + FuzzyHashQRatios::MAX_DISTANCE
515 + (match config {
516 ComparisonConfiguration::Default => FuzzyHashLengthEncoding::MAX_DISTANCE,
517 ComparisonConfiguration::NoDistance => 0,
518 })
519 }
520
521 #[inline]
522 fn compare_with_config(&self, other: &Self, config: ComparisonConfiguration) -> u32 {
523 self.body.compare(&other.body)
524 + self.checksum.compare(&other.checksum)
525 + self.qratios.compare(&other.qratios)
526 + (match config {
527 ComparisonConfiguration::Default => self.lvalue.compare(&other.lvalue),
528 ComparisonConfiguration::NoDistance => 0,
529 })
530 }
531 }
532
533 impl<
534 const SIZE_CKSUM: usize,
535 const SIZE_BODY: usize,
536 const SIZE_BUCKETS: usize,
537 const SIZE_IN_BYTES: usize,
538 const SIZE_IN_STR_BYTES: usize,
539 > TryFrom<&[u8; SIZE_IN_BYTES]>
540 for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
541 where
542 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
543 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
544 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
545 VerboseFuzzyHashParams<
546 SIZE_CKSUM,
547 SIZE_BODY,
548 SIZE_BUCKETS,
549 SIZE_IN_BYTES,
550 SIZE_IN_STR_BYTES,
551 >: ConstrainedVerboseFuzzyHashParams,
552 {
553 type Error = ParseError;
554
555 #[inline]
556 fn try_from(value: &[u8; SIZE_IN_BYTES]) -> Result<Self, Self::Error> {
557 let checksum = FuzzyHashChecksumData::<SIZE_CKSUM, SIZE_BUCKETS>::from_raw(
558 value[0..SIZE_CKSUM].try_into().unwrap(),
559 );
560 #[cfg(feature = "strict-parser")]
561 if !checksum.is_valid() {
562 return Err(ParseError::InvalidChecksum);
563 }
564 let lvalue = FuzzyHashLengthEncoding::from_raw(value[SIZE_CKSUM]);
565 #[cfg(feature = "strict-parser")]
566 if !lvalue.is_valid() {
567 return Err(ParseError::LengthIsTooLarge);
568 }
569 let qratios = FuzzyHashQRatios::from_raw(value[SIZE_CKSUM + 1]);
570 let value = &value[SIZE_CKSUM + 2..];
571 optionally_unsafe! {
572 invariant!(value.len() == SIZE_BODY);
573 }
574 Ok(Self {
575 checksum,
576 lvalue,
577 qratios,
578 body: FuzzyHashBodyData::from_raw(value.try_into().unwrap()),
579 })
580 }
581 }
582
583 impl<
584 const SIZE_CKSUM: usize,
585 const SIZE_BODY: usize,
586 const SIZE_BUCKETS: usize,
587 const SIZE_IN_BYTES: usize,
588 const SIZE_IN_STR_BYTES: usize,
589 > TryFrom<&[u8]>
590 for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
591 where
592 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
593 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
594 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
595 VerboseFuzzyHashParams<
596 SIZE_CKSUM,
597 SIZE_BODY,
598 SIZE_BUCKETS,
599 SIZE_IN_BYTES,
600 SIZE_IN_STR_BYTES,
601 >: ConstrainedVerboseFuzzyHashParams,
602 {
603 type Error = ParseError;
604
605 #[inline(always)]
606 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
607 if value.len() != SIZE_IN_BYTES {
608 return Err(ParseError::InvalidStringLength);
609 }
610 let value: &[u8; SIZE_IN_BYTES] = value.try_into().unwrap();
611 Self::try_from(value)
612 }
613 }
614
615 impl<
616 const SIZE_CKSUM: usize,
617 const SIZE_BODY: usize,
618 const SIZE_BUCKETS: usize,
619 const SIZE_IN_BYTES: usize,
620 const SIZE_IN_STR_BYTES: usize,
621 > FromStr
622 for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
623 where
624 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
625 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
626 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
627 VerboseFuzzyHashParams<
628 SIZE_CKSUM,
629 SIZE_BODY,
630 SIZE_BUCKETS,
631 SIZE_IN_BYTES,
632 SIZE_IN_STR_BYTES,
633 >: ConstrainedVerboseFuzzyHashParams,
634 {
635 type Err = ParseError;
636 #[inline(always)]
637 fn from_str(s: &str) -> Result<Self, Self::Err> {
638 Self::from_str_with(s, None)
639 }
640 }
641
642 impl<
643 const SIZE_CKSUM: usize,
644 const SIZE_BODY: usize,
645 const SIZE_BUCKETS: usize,
646 const SIZE_IN_BYTES: usize,
647 const SIZE_IN_STR_BYTES: usize,
648 > Display
649 for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
650 where
651 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
652 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
653 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
654 VerboseFuzzyHashParams<
655 SIZE_CKSUM,
656 SIZE_BODY,
657 SIZE_BUCKETS,
658 SIZE_IN_BYTES,
659 SIZE_IN_STR_BYTES,
660 >: ConstrainedVerboseFuzzyHashParams,
661 {
662 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
663 let mut buf = [0u8; SIZE_IN_STR_BYTES];
664 self.store_into_str_bytes(&mut buf, HexStringPrefix::WithVersion)
665 .unwrap();
666 cfg_if::cfg_if! {
667 if #[cfg(feature = "unsafe")] {
668 unsafe {
669 f.write_str(core::str::from_utf8_unchecked(&buf))
670 }
671 } else {
672 f.write_str(core::str::from_utf8(&buf).unwrap())
673 }
674 }
675 }
676 }
677
678 #[cfg(feature = "serde")]
679 impl<
680 const SIZE_CKSUM: usize,
681 const SIZE_BODY: usize,
682 const SIZE_BUCKETS: usize,
683 const SIZE_IN_BYTES: usize,
684 const SIZE_IN_STR_BYTES: usize,
685 > Serialize
686 for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
687 where
688 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
689 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
690 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
691 VerboseFuzzyHashParams<
692 SIZE_CKSUM,
693 SIZE_BODY,
694 SIZE_BUCKETS,
695 SIZE_IN_BYTES,
696 SIZE_IN_STR_BYTES,
697 >: ConstrainedVerboseFuzzyHashParams,
698 {
699 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
700 where
701 S: serde::Serializer,
702 {
703 if serializer.is_human_readable() {
704 let mut buffer = [0u8; SIZE_IN_STR_BYTES];
705 self.store_into_str_bytes(&mut buffer, HexStringPrefix::WithVersion)
706 .unwrap();
707 #[cfg(feature = "unsafe")]
708 unsafe {
709 serializer.serialize_str(core::str::from_utf8_unchecked(&buffer))
710 }
711 #[cfg(not(feature = "unsafe"))]
712 {
713 serializer.serialize_str(core::str::from_utf8(&buffer).unwrap())
714 }
715 } else {
716 let mut buffer = [0u8; SIZE_IN_BYTES];
717 self.store_into_bytes(&mut buffer).unwrap();
718 serializer.serialize_bytes(&buffer)
719 }
720 }
721 }
722
723 #[cfg(feature = "serde")]
732 struct FuzzyHashStringVisitor<
733 const SIZE_CKSUM: usize,
734 const SIZE_BODY: usize,
735 const SIZE_BUCKETS: usize,
736 const SIZE_IN_BYTES: usize,
737 const SIZE_IN_STR_BYTES: usize,
738 >
739 where
740 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
741 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
742 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
743 VerboseFuzzyHashParams<
744 SIZE_CKSUM,
745 SIZE_BODY,
746 SIZE_BUCKETS,
747 SIZE_IN_BYTES,
748 SIZE_IN_STR_BYTES,
749 >: ConstrainedVerboseFuzzyHashParams;
750
751 #[cfg(feature = "serde")]
752 impl<
753 const SIZE_CKSUM: usize,
754 const SIZE_BODY: usize,
755 const SIZE_BUCKETS: usize,
756 const SIZE_IN_BYTES: usize,
757 const SIZE_IN_STR_BYTES: usize,
758 > Visitor<'_>
759 for FuzzyHashStringVisitor<
760 SIZE_CKSUM,
761 SIZE_BODY,
762 SIZE_BUCKETS,
763 SIZE_IN_BYTES,
764 SIZE_IN_STR_BYTES,
765 >
766 where
767 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
768 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
769 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
770 VerboseFuzzyHashParams<
771 SIZE_CKSUM,
772 SIZE_BODY,
773 SIZE_BUCKETS,
774 SIZE_IN_BYTES,
775 SIZE_IN_STR_BYTES,
776 >: ConstrainedVerboseFuzzyHashParams,
777 {
778 type Value =
779 FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>;
780
781 fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
782 formatter.write_str("a fuzzy hash string")
783 }
784
785 #[inline]
786 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
787 where
788 E: serde::de::Error,
789 {
790 self.visit_bytes(v.as_bytes())
791 }
792
793 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
794 where
795 E: serde::de::Error,
796 {
797 Self::Value::from_str_bytes(v, None).map_err(serde::de::Error::custom::<ParseError>)
798 }
799 }
800
801 #[cfg(feature = "serde")]
809 struct FuzzyHashBytesVisitor<
810 const SIZE_CKSUM: usize,
811 const SIZE_BODY: usize,
812 const SIZE_BUCKETS: usize,
813 const SIZE_IN_BYTES: usize,
814 const SIZE_IN_STR_BYTES: usize,
815 >
816 where
817 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
818 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
819 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
820 VerboseFuzzyHashParams<
821 SIZE_CKSUM,
822 SIZE_BODY,
823 SIZE_BUCKETS,
824 SIZE_IN_BYTES,
825 SIZE_IN_STR_BYTES,
826 >: ConstrainedVerboseFuzzyHashParams;
827
828 #[cfg(feature = "serde")]
829 impl<
830 const SIZE_CKSUM: usize,
831 const SIZE_BODY: usize,
832 const SIZE_BUCKETS: usize,
833 const SIZE_IN_BYTES: usize,
834 const SIZE_IN_STR_BYTES: usize,
835 > Visitor<'_>
836 for FuzzyHashBytesVisitor<
837 SIZE_CKSUM,
838 SIZE_BODY,
839 SIZE_BUCKETS,
840 SIZE_IN_BYTES,
841 SIZE_IN_STR_BYTES,
842 >
843 where
844 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
845 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
846 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
847 VerboseFuzzyHashParams<
848 SIZE_CKSUM,
849 SIZE_BODY,
850 SIZE_BUCKETS,
851 SIZE_IN_BYTES,
852 SIZE_IN_STR_BYTES,
853 >: ConstrainedVerboseFuzzyHashParams,
854 {
855 type Value =
856 FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>;
857
858 fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
859 formatter.write_str("struct FuzzyHash")
860 }
861
862 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
863 where
864 E: serde::de::Error,
865 {
866 if v.len() != SIZE_IN_BYTES {
867 return Err(serde::de::Error::invalid_length(v.len(), &self));
868 }
869 Ok(Self::Value::try_from(v).unwrap())
870 }
871 }
872
873 #[cfg(feature = "serde")]
874 impl<
875 'de,
876 const SIZE_CKSUM: usize,
877 const SIZE_BODY: usize,
878 const SIZE_BUCKETS: usize,
879 const SIZE_IN_BYTES: usize,
880 const SIZE_IN_STR_BYTES: usize,
881 > Deserialize<'de>
882 for FuzzyHash<SIZE_CKSUM, SIZE_BODY, SIZE_BUCKETS, SIZE_IN_BYTES, SIZE_IN_STR_BYTES>
883 where
884 FuzzyHashBodyData<SIZE_BODY>: FuzzyHashBody,
885 FuzzyHashBucketsInfo<SIZE_BUCKETS>: FuzzyHashBucketMapper,
886 FuzzyHashChecksumData<SIZE_CKSUM, SIZE_BUCKETS>: FuzzyHashChecksum,
887 VerboseFuzzyHashParams<
888 SIZE_CKSUM,
889 SIZE_BODY,
890 SIZE_BUCKETS,
891 SIZE_IN_BYTES,
892 SIZE_IN_STR_BYTES,
893 >: ConstrainedVerboseFuzzyHashParams,
894 {
895 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
896 where
897 D: serde::Deserializer<'de>,
898 {
899 if deserializer.is_human_readable() {
900 #[cfg(feature = "serde-buffered")]
901 {
902 deserializer.deserialize_string(
903 FuzzyHashStringVisitor::<
904 SIZE_CKSUM,
905 SIZE_BODY,
906 SIZE_BUCKETS,
907 SIZE_IN_BYTES,
908 SIZE_IN_STR_BYTES,
909 >,
910 )
911 }
912 #[cfg(not(feature = "serde-buffered"))]
913 {
914 deserializer.deserialize_str(
915 FuzzyHashStringVisitor::<
916 SIZE_CKSUM,
917 SIZE_BODY,
918 SIZE_BUCKETS,
919 SIZE_IN_BYTES,
920 SIZE_IN_STR_BYTES,
921 >,
922 )
923 }
924 } else {
925 #[cfg(feature = "serde-buffered")]
926 {
927 deserializer.deserialize_byte_buf(
928 FuzzyHashBytesVisitor::<
929 SIZE_CKSUM,
930 SIZE_BODY,
931 SIZE_BUCKETS,
932 SIZE_IN_BYTES,
933 SIZE_IN_STR_BYTES,
934 >,
935 )
936 }
937 #[cfg(not(feature = "serde-buffered"))]
938 {
939 deserializer.deserialize_bytes(
940 FuzzyHashBytesVisitor::<
941 SIZE_CKSUM,
942 SIZE_BODY,
943 SIZE_BUCKETS,
944 SIZE_IN_BYTES,
945 SIZE_IN_STR_BYTES,
946 >,
947 )
948 }
949 }
950 }
951 }
952}
953
954macro_rules! inner_type {
957 ($size_checksum:expr, $size_buckets:expr) => {
958 <FuzzyHashParams<{$size_checksum}, {$size_buckets}> as ConstrainedFuzzyHashParams>::InnerFuzzyHashType
959 };
960}
961
962#[derive(Debug, Clone, Copy, PartialEq, Eq)]
989pub struct FuzzyHash<const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize>
990where
991 FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
992{
993 inner: inner_type!(SIZE_CKSUM, SIZE_BUCKETS),
995}
996
997impl<const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
998where
999 FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1000{
1001 #[inline(always)]
1003 pub(crate) fn new(inner: inner_type!(SIZE_CKSUM, SIZE_BUCKETS)) -> Self {
1004 Self { inner }
1005 }
1006}
1007
1008impl<const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> crate::FuzzyHashType
1009 for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1010where
1011 FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1012{
1013 type ChecksumType = <inner_type!(SIZE_CKSUM, SIZE_BUCKETS) as FuzzyHashType>::ChecksumType;
1014 type BodyType = <inner_type!(SIZE_CKSUM, SIZE_BUCKETS) as FuzzyHashType>::BodyType;
1015
1016 const NUMBER_OF_BUCKETS: usize = <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::NUMBER_OF_BUCKETS;
1017 const SIZE_IN_BYTES: usize = <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::SIZE_IN_BYTES;
1018 const LEN_IN_STR_EXCEPT_PREFIX: usize =
1019 <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::LEN_IN_STR_EXCEPT_PREFIX;
1020 const LEN_IN_STR: usize = <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::LEN_IN_STR;
1021 #[inline(always)]
1022 fn from_str_bytes(
1023 bytes: &[u8],
1024 prefix: Option<HexStringPrefix>,
1025 ) -> Result<Self, crate::errors::ParseError> {
1026 <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::from_str_bytes(bytes, prefix)
1027 .map(|inner| Self { inner })
1028 }
1029 #[inline(always)]
1030 fn checksum(&self) -> &Self::ChecksumType {
1031 self.inner.checksum()
1032 }
1033 #[inline(always)]
1034 fn length(&self) -> &FuzzyHashLengthEncoding {
1035 self.inner.length()
1036 }
1037 #[inline(always)]
1038 fn qratios(&self) -> &FuzzyHashQRatios {
1039 self.inner.qratios()
1040 }
1041 #[inline(always)]
1042 fn body(&self) -> &Self::BodyType {
1043 self.inner.body()
1044 }
1045 #[inline(always)]
1046 fn store_into_bytes(&self, out: &mut [u8]) -> Result<usize, crate::errors::OperationError> {
1047 self.inner.store_into_bytes(out)
1048 }
1049 #[inline(always)]
1050 fn store_into_str_bytes(
1051 &self,
1052 out: &mut [u8],
1053 prefix: HexStringPrefix,
1054 ) -> Result<usize, OperationError> {
1055 self.inner.store_into_str_bytes(out, prefix)
1056 }
1057 #[inline(always)]
1058 fn max_distance(config: ComparisonConfiguration) -> u32 {
1059 <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::max_distance(config)
1060 }
1061 #[inline(always)]
1062 fn compare_with_config(&self, other: &Self, config: ComparisonConfiguration) -> u32 {
1063 self.inner.compare_with_config(&other.inner, config)
1064 }
1065}
1066impl<const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> Display
1067 for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1068where
1069 FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1070{
1071 #[inline(always)]
1072 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1073 self.inner.fmt(f)
1074 }
1075}
1076impl<const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> FromStr
1077 for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1078where
1079 FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1080 inner_type!(SIZE_CKSUM, SIZE_BUCKETS): FromStr<Err = ParseError>,
1081{
1082 type Err = ParseError;
1083 #[inline(always)]
1084 fn from_str(s: &str) -> Result<Self, Self::Err> {
1085 <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::from_str(s).map(Self::new)
1086 }
1087}
1088impl<'a, const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize, const SIZE_IN_BYTES: usize>
1089 TryFrom<&'a [u8; SIZE_IN_BYTES]> for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1090where
1091 FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1092 inner_type!(SIZE_CKSUM, SIZE_BUCKETS): TryFrom<&'a [u8; SIZE_IN_BYTES], Error = ParseError>,
1093{
1094 type Error = ParseError;
1095 #[inline(always)]
1096 fn try_from(value: &'a [u8; SIZE_IN_BYTES]) -> Result<Self, Self::Error> {
1097 <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::try_from(value).map(Self::new)
1098 }
1099}
1100impl<'a, const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> TryFrom<&'a [u8]>
1101 for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1102where
1103 FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1104 inner_type!(SIZE_CKSUM, SIZE_BUCKETS): TryFrom<&'a [u8], Error = ParseError>,
1105{
1106 type Error = ParseError;
1107 #[inline(always)]
1108 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
1109 <inner_type!(SIZE_CKSUM, SIZE_BUCKETS)>::try_from(value).map(Self::new)
1110 }
1111}
1112#[cfg(feature = "serde")]
1113impl<const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> Serialize
1114 for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1115where
1116 FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1117 inner_type!(SIZE_CKSUM, SIZE_BUCKETS): Serialize,
1118{
1119 #[inline(always)]
1120 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1121 where
1122 S: serde::Serializer,
1123 {
1124 <inner_type!(SIZE_CKSUM, SIZE_BUCKETS) as Serialize>::serialize(&self.inner, serializer)
1126 }
1127}
1128#[cfg(feature = "serde")]
1129impl<'de, const SIZE_CKSUM: usize, const SIZE_BUCKETS: usize> Deserialize<'de>
1130 for FuzzyHash<SIZE_CKSUM, SIZE_BUCKETS>
1131where
1132 FuzzyHashParams<SIZE_CKSUM, SIZE_BUCKETS>: ConstrainedFuzzyHashParams,
1133 inner_type!(SIZE_CKSUM, SIZE_BUCKETS): Deserialize<'de>,
1134{
1135 #[inline(always)]
1136 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1137 where
1138 D: serde::Deserializer<'de>,
1139 {
1140 <inner_type!(SIZE_CKSUM, SIZE_BUCKETS) as Deserialize<'de>>::deserialize(deserializer)
1142 .map(Self::new)
1143 }
1144}
1145
1146mod tests;