ssdeep/internals/
compare_easy.rs

1// SPDX-License-Identifier: MIT
2// SPDX-FileCopyrightText: Copyright (C) 2023–2025 Tsukasa OI <floss_ssdeep@irq.a4lg.com>.
3
4//! Easy comparison functions and related error reporting utilities.
5
6#![cfg(feature = "easy-functions")]
7
8#[cfg(all(not(feature = "std"), ffuzzy_error_in_core = "stable"))]
9use core::error::Error;
10#[cfg(feature = "std")]
11use std::error::Error;
12
13use crate::internals::hash::parser_state::{
14    ParseError, ParseErrorInfo, ParseErrorKind, ParseErrorOrigin,
15};
16use crate::internals::hash::LongFuzzyHash;
17
18/// The operand (side) which caused a parse error.
19///
20/// # Compatibility Note
21///
22/// Since the version 0.3, the representation of this enum is no longer
23/// specified as specific representation of this enum is not important.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum ParseErrorSide {
26    /// The left hand side.
27    Left,
28
29    /// The right hand side.
30    Right,
31}
32
33/// The error type representing a parse error for one of the operands
34/// specified to the [`compare()`] function.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct ParseErrorEither(ParseErrorSide, ParseError); // grcov-excl-br-line:STRUCT_MEMBER
37
38impl ParseErrorEither {
39    /// Returns which operand caused a parse error.
40    pub fn side(&self) -> ParseErrorSide {
41        self.0
42    }
43}
44
45impl ParseErrorInfo for ParseErrorEither {
46    fn kind(&self) -> ParseErrorKind {
47        self.1.kind()
48    }
49    fn origin(&self) -> ParseErrorOrigin {
50        self.1.origin()
51    }
52    fn offset(&self) -> usize {
53        self.1.offset()
54    }
55}
56
57impl core::fmt::Display for ParseErrorEither {
58    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59        write!(
60            f,
61            "error occurred while parsing fuzzy hash {3} ({1}, at byte offset {2}): {0}",
62            self.kind(),
63            self.origin(),
64            self.offset(),
65            match self.side() {
66                ParseErrorSide::Left => 1,
67                ParseErrorSide::Right => 2,
68            }
69        )
70    }
71}
72
73crate::internals::macros::impl_error!(ParseErrorEither {
74    fn source(&self) -> Option<&(dyn Error + 'static)> {
75        Some(&self.1)
76    }
77});
78
79/// Compare two fuzzy hashes.
80///
81/// If a parse error occurs, [`Err`] containing
82/// [a parse error](ParseErrorEither) is returned.
83/// Otherwise, [`Ok`] containing the similarity score (`0..=100`) is returned.
84///
85/// # Example
86///
87/// ```
88/// assert_eq!(
89///     ssdeep::compare(
90///         "6:3ll7QzDkmJmMHkQoO/llSZEnEuLszmbMAWn:VqDk5QtLbW",
91///         "6:3ll7QzDkmQjmMoDHglHOxPWT0lT0lT0lB:VqDk+n"
92///     ).unwrap(),
93///     46
94/// );
95/// ```
96pub fn compare(lhs: &str, rhs: &str) -> Result<u32, ParseErrorEither> {
97    let lhs: LongFuzzyHash = match str::parse(lhs) {
98        Ok(value) => value,
99        Err(err) => {
100            return Err(ParseErrorEither(ParseErrorSide::Left, err));
101        }
102    };
103    let rhs: LongFuzzyHash = match str::parse(rhs) {
104        Ok(value) => value,
105        Err(err) => {
106            return Err(ParseErrorEither(ParseErrorSide::Right, err));
107        }
108    };
109    Ok(lhs.compare(rhs.as_ref()))
110}
111
112mod tests;