fif/src/findings.rs

95 lines
2.9 KiB
Rust

//! The [`Findings`] and [`ScanError`] structs, used for conveying whether a given file was able to be scanned and
//! whether its MIME type could be inferred.
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
use std::path::{Path, PathBuf};
use mime::Mime;
#[cfg(feature = "json")]
use serde::{ser::SerializeStruct, Serializer};
use crate::files::mime_extension_lookup;
use crate::String;
/// Information about a scanned file.
#[derive(Eq, PartialEq)]
pub struct Findings {
/// The location of the scanned file.
pub file: PathBuf,
/// Whether or not the file's extension is valid for its mimetype.
pub valid: bool,
/// The file's mimetype.
pub mime: Mime,
}
impl PartialOrd<Self> for Findings {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Ord for Findings {
fn cmp(&self, other: &Self) -> Ordering {
// files with no recommended extension should appear first, so that fif outputs the "no known extension for x"
// comments before the "mv x y" instructions
// since fif doesn't output anything for valid files, the comparison will consider any comparison involving a
// valid Findings to be equal, avoiding the (somewhat) expensive call to recommended_extension. after all, since
// fif never displays valid files, it really doesn't matter what position they end up in.
if self.valid || other.valid {
return Ordering::Equal;
}
match (self.recommended_extension(), other.recommended_extension()) {
(None, Some(_)) => Ordering::Greater,
(Some(_), None) => Ordering::Less,
_ => self.file.cmp(&other.file),
}
}
}
#[cfg(feature = "json")]
impl serde::Serialize for Findings {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// the second parameter is the number of fields in the struct -- in this case, 3
let mut state = serializer.serialize_struct("Findings", 3)?;
state.serialize_field("file", &self.file)?;
state.serialize_field("valid", &self.valid)?;
state.serialize_field("mime", &self.mime.essence_str())?;
state.end()
}
}
impl Findings {
pub fn recommended_extension(&self) -> Option<String> {
mime_extension_lookup(self.mime.essence_str().into()).map(|extensions| extensions[0].clone())
}
}
#[derive(Debug, PartialEq, PartialOrd, Ord, Eq)]
#[cfg_attr(feature = "json", derive(serde::Serialize))]
#[cfg_attr(feature = "json", serde(tag = "type", content = "path"))]
pub enum ScanError<'a> {
/// Something went wrong while trying to read the given file.
File(&'a Path),
/// Failed to determine the mimetype of the given file.
Mime(&'a Path),
}
impl<'a> Display for ScanError<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Couldn't {} file: {}",
match self {
Self::File(_) => "read",
Self::Mime(_) => "determine mime type of",
},
match self {
Self::File(f) | Self::Mime(f) => f.to_string_lossy(),
}
)
}
}