stern4rust/source_reader.rs
1// Copyright 2025 Umberto Gotti <umberto.gotti@umbertogotti.dev>
2// Licensed under the MIT License
3// SPDX-License-Identifier: MIT
4
5use std::fs;
6use std::path::Path;
7
8use crate::manifest_resolver::ManifestResolver;
9use crate::offence::Offence;
10use crate::rules::readable_source_rule::ReadableSourceRule;
11use crate::source_file::SourceFile;
12
13// Reads a file the walker found, and turns a failure into a finding rather than
14// into the end of the run.
15//
16// A file that cannot be read used to abort everything, so one unreadable file
17// hid every offence already found in every other file. That is the wrong trade.
18// A bad manifest is genuinely a could-not-run condition -- without it nothing
19// can be enumerated -- but a single unreadable file is a fact about the tree,
20// and the rest of the tree is still worth reporting on.
21pub struct SourceReader;
22
23impl SourceReader {
24 // The offence is boxed because it is much larger than the SourceFile it is
25 // returned instead of, and an unboxed Err variant that size makes every
26 // successful read pay for the failing one.
27 pub fn read(root: &Path, path: &Path) -> Result<SourceFile, Box<Offence>> {
28 let relative = ManifestResolver::relative_to(root, path);
29 match fs::read_to_string(path) {
30 Ok(contents) => Ok(SourceFile::new(&relative, &contents)),
31 Err(error) => Err(Box::new(
32 Offence::new(
33 &relative,
34 1,
35 ReadableSourceRule::NAME,
36 format!("file could not be read: {error}"),
37 "check that the file exists and that its permissions allow reading it"
38 .to_string(),
39 )
40 .with_subject(&relative),
41 )),
42 }
43 }
44}