Skip to content

Commit 9c285cf

Browse files
committed
extracting separate concerns from runner
1 parent 470ac7b commit 9c285cf

File tree

6 files changed

+257
-185
lines changed

6 files changed

+257
-185
lines changed

src/config.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use serde::Deserialize;
2+
use std::{fs::File, path::Path};
23

34
#[derive(Deserialize, Debug, Clone)]
45
pub struct Config {
@@ -77,6 +78,13 @@ fn default_ignore_dirs() -> Vec<String> {
7778
]
7879
}
7980

81+
impl Config {
82+
pub fn load_from_path(path: &Path) -> std::result::Result<Self, String> {
83+
let file = File::open(path).map_err(|e| format!("Can't open config file: {} ({})", path.to_string_lossy(), e))?;
84+
serde_yaml::from_reader(file).map_err(|e| format!("Can't parse config file: {} ({})", path.to_string_lossy(), e))
85+
}
86+
}
87+
8088
#[cfg(test)]
8189
mod tests {
8290
use std::{

src/ownership.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::{
1010
use tracing::{info, instrument};
1111

1212
pub(crate) mod codeowners_file_parser;
13+
pub(crate) mod codeowners_query;
1314
mod file_generator;
1415
mod file_owner_finder;
1516
pub mod for_file_fast;

src/ownership/codeowners_query.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use std::collections::HashMap;
2+
use std::path::{Path, PathBuf};
3+
4+
use crate::ownership::codeowners_file_parser::Parser;
5+
use crate::project::Team;
6+
7+
pub(crate) fn team_for_file_from_codeowners(
8+
project_root: &Path,
9+
codeowners_file_path: &Path,
10+
team_file_globs: &[String],
11+
file_path: &Path,
12+
) -> Result<Option<Team>, String> {
13+
let relative_file_path = if file_path.is_absolute() {
14+
file_path.strip_prefix(project_root).unwrap_or(file_path).to_path_buf()
15+
} else {
16+
PathBuf::from(file_path)
17+
};
18+
19+
let parser = Parser {
20+
codeowners_file_path: codeowners_file_path.to_path_buf(),
21+
project_root: project_root.to_path_buf(),
22+
team_file_globs: team_file_globs.to_vec(),
23+
};
24+
25+
parser.team_from_file_path(&relative_file_path).map_err(|e| e.to_string())
26+
}
27+
28+
pub(crate) fn teams_for_files_from_codeowners(
29+
project_root: &Path,
30+
codeowners_file_path: &Path,
31+
team_file_globs: &[String],
32+
file_paths: &[String],
33+
) -> Result<HashMap<String, Option<Team>>, String> {
34+
let relative_file_paths: Vec<PathBuf> = file_paths
35+
.iter()
36+
.map(Path::new)
37+
.map(|path| {
38+
if path.is_absolute() {
39+
path.strip_prefix(project_root).unwrap_or(path).to_path_buf()
40+
} else {
41+
path.to_path_buf()
42+
}
43+
})
44+
.collect();
45+
46+
let parser = Parser {
47+
codeowners_file_path: codeowners_file_path.to_path_buf(),
48+
project_root: project_root.to_path_buf(),
49+
team_file_globs: team_file_globs.to_vec(),
50+
};
51+
52+
parser.teams_from_files_paths(&relative_file_paths).map_err(|e| e.to_string())
53+
}

src/runner.rs

Lines changed: 68 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,33 @@
1-
use core::fmt;
2-
use std::{
3-
collections::HashMap,
4-
fs::File,
5-
path::{Path, PathBuf},
6-
process::Command,
7-
};
1+
use std::{path::Path, process::Command};
82

9-
use error_stack::{Context, Result, ResultExt};
10-
use serde::{Deserialize, Serialize};
3+
use error_stack::{Result, ResultExt};
114

125
use crate::{
136
cache::{Cache, Caching, file::GlobalCache, noop::NoopCache},
147
config::Config,
158
ownership::{FileOwner, Ownership},
16-
project::Team,
179
project_builder::ProjectBuilder,
1810
};
1911

20-
#[derive(Debug, Default, Serialize, Deserialize)]
21-
pub struct RunResult {
22-
pub validation_errors: Vec<String>,
23-
pub io_errors: Vec<String>,
24-
pub info_messages: Vec<String>,
25-
}
26-
#[derive(Debug, Clone)]
27-
pub struct RunConfig {
28-
pub project_root: PathBuf,
29-
pub codeowners_file_path: PathBuf,
30-
pub config_path: PathBuf,
31-
pub no_cache: bool,
32-
}
12+
mod types;
13+
pub use self::types::{Error, RunConfig, RunResult};
14+
mod api;
15+
pub use self::api::*;
3316

3417
pub struct Runner {
3518
run_config: RunConfig,
3619
ownership: Ownership,
3720
cache: Cache,
38-
}
39-
40-
pub fn for_file(run_config: &RunConfig, file_path: &str, from_codeowners: bool) -> RunResult {
41-
if from_codeowners {
42-
return for_file_codeowners_only(run_config, file_path);
43-
}
44-
for_file_optimized(run_config, file_path)
45-
}
46-
47-
pub fn file_owner_for_file(run_config: &RunConfig, file_path: &str) -> Result<Option<FileOwner>, Error> {
48-
let config = config_from_path(&run_config.config_path)?;
49-
use crate::ownership::for_file_fast::find_file_owners;
50-
let owners = find_file_owners(&run_config.project_root, &config, std::path::Path::new(file_path)).map_err(Error::Io)?;
51-
Ok(owners.first().cloned())
52-
}
53-
54-
pub fn team_for_file(run_config: &RunConfig, file_path: &str) -> Result<Option<Team>, Error> {
55-
let owner = file_owner_for_file(run_config, file_path)?;
56-
Ok(owner.map(|fo| fo.team.clone()))
21+
config: Config,
5722
}
5823

5924
pub fn version() -> String {
6025
env!("CARGO_PKG_VERSION").to_string()
6126
}
6227

63-
pub fn for_team(run_config: &RunConfig, team_name: &str) -> RunResult {
64-
run_with_runner(run_config, |runner| runner.for_team(team_name))
65-
}
66-
67-
pub fn validate(run_config: &RunConfig, _file_paths: Vec<String>) -> RunResult {
68-
run_with_runner(run_config, |runner| runner.validate())
69-
}
70-
71-
pub fn generate(run_config: &RunConfig, git_stage: bool) -> RunResult {
72-
run_with_runner(run_config, |runner| runner.generate(git_stage))
73-
}
74-
75-
pub fn generate_and_validate(run_config: &RunConfig, _file_paths: Vec<String>, git_stage: bool) -> RunResult {
76-
run_with_runner(run_config, |runner| runner.generate_and_validate(git_stage))
77-
}
78-
79-
pub fn delete_cache(run_config: &RunConfig) -> RunResult {
80-
run_with_runner(run_config, |runner| runner.delete_cache())
81-
}
82-
83-
pub fn crosscheck_owners(run_config: &RunConfig) -> RunResult {
84-
run_with_runner(run_config, |runner| runner.crosscheck_owners())
85-
}
86-
8728
pub type Runnable = fn(Runner) -> RunResult;
8829

89-
pub fn run_with_runner<F>(run_config: &RunConfig, runnable: F) -> RunResult
30+
pub fn run<F>(run_config: &RunConfig, runnable: F) -> RunResult
9031
where
9132
F: FnOnce(Runner) -> RunResult,
9233
{
@@ -102,35 +43,12 @@ where
10243
runnable(runner)
10344
}
10445

105-
impl RunResult {
106-
pub fn has_errors(&self) -> bool {
107-
!self.validation_errors.is_empty() || !self.io_errors.is_empty()
108-
}
109-
}
110-
111-
#[derive(Debug)]
112-
pub enum Error {
113-
Io(String),
114-
ValidationFailed,
115-
}
116-
117-
impl Context for Error {}
118-
impl fmt::Display for Error {
119-
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
120-
match self {
121-
Error::Io(msg) => fmt.write_str(msg),
122-
Error::ValidationFailed => fmt.write_str("Error::ValidationFailed"),
123-
}
46+
pub(crate) fn config_from_path(path: &Path) -> Result<Config, Error> {
47+
match crate::config::Config::load_from_path(path) {
48+
Ok(c) => Ok(c),
49+
Err(msg) => Err(error_stack::Report::new(Error::Io(msg))),
12450
}
12551
}
126-
127-
pub(crate) fn config_from_path(path: &PathBuf) -> Result<Config, Error> {
128-
let config_file = File::open(path)
129-
.change_context(Error::Io(format!("Can't open config file: {}", &path.to_string_lossy())))
130-
.attach_printable(format!("Can't open config file: {}", &path.to_string_lossy()))?;
131-
132-
serde_yaml::from_reader(config_file).change_context(Error::Io(format!("Can't parse config file: {}", &path.to_string_lossy())))
133-
}
13452
impl Runner {
13553
pub fn new(run_config: &RunConfig) -> Result<Self, Error> {
13654
let config = config_from_path(&run_config.config_path)?;
@@ -168,6 +86,7 @@ impl Runner {
16886
run_config: run_config.clone(),
16987
ownership,
17088
cache,
89+
config,
17190
})
17291
}
17392

@@ -255,111 +174,75 @@ impl Runner {
255174
pub fn crosscheck_owners(&self) -> RunResult {
256175
crate::crosscheck::crosscheck_owners(&self.run_config, &self.cache)
257176
}
258-
}
259177

260-
fn for_file_codeowners_only(run_config: &RunConfig, file_path: &str) -> RunResult {
261-
match team_for_file_from_codeowners(run_config, file_path) {
262-
Ok(Some(team)) => {
263-
let relative_team_path = team
264-
.path
265-
.strip_prefix(&run_config.project_root)
266-
.unwrap_or(team.path.as_path())
267-
.to_string_lossy()
268-
.to_string();
269-
RunResult {
270-
info_messages: vec![format!(
271-
"Team: {}\nGithub Team: {}\nTeam YML: {}\nDescription:\n- Owner inferred from codeowners file",
272-
team.name, team.github_team, relative_team_path
273-
)],
274-
..Default::default()
275-
}
276-
}
277-
Ok(None) => RunResult::default(),
278-
Err(err) => RunResult {
279-
io_errors: vec![err.to_string()],
280-
..Default::default()
281-
},
178+
pub fn owners_for_file(&self, file_path: &str) -> Result<Vec<FileOwner>, Error> {
179+
use crate::ownership::for_file_fast::find_file_owners;
180+
let owners = find_file_owners(&self.run_config.project_root, &self.config, std::path::Path::new(file_path)).map_err(Error::Io)?;
181+
Ok(owners)
282182
}
283-
}
284183

285-
// For an array of file paths, return a map of file path to its owning team
286-
pub fn teams_for_files_from_codeowners(run_config: &RunConfig, file_paths: &[String]) -> Result<HashMap<String, Option<Team>>, Error> {
287-
let relative_file_paths: Vec<PathBuf> = file_paths
288-
.iter()
289-
.map(|path| Path::new(path).strip_prefix(&run_config.project_root).unwrap_or(Path::new(path)))
290-
.map(|path| path.to_path_buf())
291-
.collect();
292-
293-
let parser = build_codeowners_parser(run_config)?;
294-
Ok(parser
295-
.teams_from_files_paths(&relative_file_paths)
296-
.map_err(|e| Error::Io(e.to_string()))?)
297-
}
298-
299-
fn build_codeowners_parser(run_config: &RunConfig) -> Result<crate::ownership::codeowners_file_parser::Parser, Error> {
300-
let config = config_from_path(&run_config.config_path)?;
301-
Ok(crate::ownership::codeowners_file_parser::Parser {
302-
codeowners_file_path: run_config.codeowners_file_path.clone(),
303-
project_root: run_config.project_root.clone(),
304-
team_file_globs: config.team_file_glob.clone(),
305-
})
306-
}
307-
308-
pub fn team_for_file_from_codeowners(run_config: &RunConfig, file_path: &str) -> Result<Option<Team>, Error> {
309-
let relative_file_path = Path::new(file_path)
310-
.strip_prefix(&run_config.project_root)
311-
.unwrap_or(Path::new(file_path));
312-
313-
let parser = build_codeowners_parser(run_config)?;
314-
Ok(parser
315-
.team_from_file_path(Path::new(relative_file_path))
316-
.map_err(|e| Error::Io(e.to_string()))?)
317-
}
318-
319-
fn for_file_optimized(run_config: &RunConfig, file_path: &str) -> RunResult {
320-
let config = match config_from_path(&run_config.config_path) {
321-
Ok(c) => c,
322-
Err(err) => {
323-
return RunResult {
324-
io_errors: vec![err.to_string()],
325-
..Default::default()
326-
};
327-
}
328-
};
184+
pub fn for_file_optimized(&self, file_path: &str) -> RunResult {
185+
let file_owners = match self.owners_for_file(file_path) {
186+
Ok(v) => v,
187+
Err(err) => {
188+
return RunResult {
189+
io_errors: vec![err.to_string()],
190+
..Default::default()
191+
};
192+
}
193+
};
329194

330-
use crate::ownership::for_file_fast::find_file_owners;
331-
let file_owners = match find_file_owners(&run_config.project_root, &config, std::path::Path::new(file_path)) {
332-
Ok(v) => v,
333-
Err(err) => {
334-
return RunResult {
335-
io_errors: vec![err],
336-
..Default::default()
337-
};
195+
let info_messages: Vec<String> = match file_owners.len() {
196+
0 => vec![format!("{}", FileOwner::default())],
197+
1 => vec![format!("{}", file_owners[0])],
198+
_ => {
199+
let mut error_messages = vec!["Error: file is owned by multiple teams!".to_string()];
200+
for file_owner in file_owners {
201+
error_messages.push(format!("\n{}", file_owner));
202+
}
203+
return RunResult {
204+
validation_errors: error_messages,
205+
..Default::default()
206+
};
207+
}
208+
};
209+
RunResult {
210+
info_messages,
211+
..Default::default()
338212
}
339-
};
213+
}
340214

341-
let info_messages: Vec<String> = match file_owners.len() {
342-
0 => vec![format!("{}", FileOwner::default())],
343-
1 => vec![format!("{}", file_owners[0])],
344-
_ => {
345-
let mut error_messages = vec!["Error: file is owned by multiple teams!".to_string()];
346-
for file_owner in file_owners {
347-
error_messages.push(format!("\n{}", file_owner));
215+
pub fn for_file_codeowners_only(&self, file_path: &str) -> RunResult {
216+
match team_for_file_from_codeowners(&self.run_config, file_path) {
217+
Ok(Some(team)) => {
218+
let relative_team_path = team
219+
.path
220+
.strip_prefix(&self.run_config.project_root)
221+
.unwrap_or(team.path.as_path())
222+
.to_string_lossy()
223+
.to_string();
224+
RunResult {
225+
info_messages: vec![format!(
226+
"Team: {}\nGithub Team: {}\nTeam YML: {}\nDescription:\n- Owner inferred from codeowners file",
227+
team.name, team.github_team, relative_team_path
228+
)],
229+
..Default::default()
230+
}
348231
}
349-
return RunResult {
350-
validation_errors: error_messages,
232+
Ok(None) => RunResult::default(),
233+
Err(err) => RunResult {
234+
io_errors: vec![err.to_string()],
351235
..Default::default()
352-
};
236+
},
353237
}
354-
};
355-
RunResult {
356-
info_messages,
357-
..Default::default()
358238
}
359239
}
360240

241+
// removed free functions for for_file_* variants in favor of Runner methods
242+
361243
#[cfg(test)]
362244
mod tests {
245+
use std::path::Path;
363246
use tempfile::tempdir;
364247

365248
use super::*;

0 commit comments

Comments
 (0)