Skip to content

Commit 3d62300

Browse files
authored
Rules v2 (#15)
1 parent 01571b0 commit 3d62300

File tree

11 files changed

+252
-207
lines changed

11 files changed

+252
-207
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rust_arkitect"
3-
version = "0.3.0"
3+
version = "0.3.1"
44
authors = ["Patrick Luca Fazzi <patrick91@live.it>"]
55
edition = "2021"
66
description = "rust_arkitect is a lightweight library for defining and validating architectural rules in Rust projects"

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Rust Arkitect helps you:
1717
Add Rust Arkitect to your `Cargo.toml`:
1818
```toml
1919
[dev-dependencies]
20-
rust_arkitect = "0.2"
20+
rust_arkitect = "0.3"
2121
```
2222
Define your architectural rules:
2323
```rust
@@ -30,13 +30,13 @@ fn test_architectural_rules() {
3030
#[rustfmt::skip]
3131
let rules = ArchitecturalRules::define()
3232
.rules_for_module("sample_project::application")
33-
.may_depend_on(&["sample_project::domain"])
33+
.it_may_depend_on(&["sample_project::domain"])
3434

3535
.rules_for_module("sample_project::domain")
36-
.must_not_depend_on_anything()
36+
.it_must_not_depend_on_anything()
3737

3838
.rules_for_module("sample_project::infrastructure")
39-
.may_depend_on(&["sample_project::domain", "sample_project::application"])
39+
.it_may_depend_on(&["sample_project::domain", "sample_project::application"])
4040

4141
.build();
4242

@@ -78,13 +78,13 @@ fn test_architecture_baseline() {
7878
#[rustfmt::skip]
7979
let rules = ArchitecturalRules::define()
8080
.rules_for_crate("application")
81-
.may_depend_on(&["domain"])
81+
.it_may_depend_on(&["domain"])
8282

8383
.rules_for_module("domain")
84-
.must_not_depend_on_anything()
84+
.it_must_not_depend_on_anything()
8585

8686
.rules_for_module("infrastructure")
87-
.may_depend_on(&["domain", "application"])
87+
.it_may_depend_on(&["domain", "application"])
8888

8989
.build();
9090

@@ -147,7 +147,7 @@ impl TestRule {
147147
// Implement Display for the rule for better readability in logs
148148
impl Display for TestRule {
149149
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
150-
write!(f, "TestRule applied")
150+
write!(f, "TestRule")
151151
}
152152
}
153153

examples/sample_project/src/architecture_rules_test.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ fn test_vertical_slices_architecture_rules() {
99
#[rustfmt::skip]
1010
let rules = ArchitecturalRules::define()
1111
.rules_for_module("sample_project::conversion")
12-
.may_depend_on(&["sample_project::contracts"])
12+
.it_may_depend_on(&["sample_project::contracts"])
1313

1414
.rules_for_module("sample_project::policy_management")
15-
.may_depend_on(&["sample_project::contracts"])
15+
.it_may_depend_on(&["sample_project::contracts"])
1616

1717
.rules_for_module("sample_project::contracts")
18-
.must_not_depend_on_anything()
18+
.it_must_not_depend_on_anything()
1919

2020
.build();
2121

@@ -35,13 +35,13 @@ fn test_mvc_architecture_rules() {
3535
#[rustfmt::skip]
3636
let rules = ArchitecturalRules::define()
3737
.rules_for_module("sample_project::policy_management::model")
38-
.must_not_depend_on_anything()
38+
.it_must_not_depend_on_anything()
3939

4040
.rules_for_module("sample_project::policy_management::repository")
41-
.may_depend_on(&["sample_project::policy_management::model"])
41+
.it_may_depend_on(&["sample_project::policy_management::model"])
4242

4343
.rules_for_module("sample_project::policy_management::controller")
44-
.may_depend_on(&["sample_project::policy_management::repository", "sample_project::policy_management::model"])
44+
.it_may_depend_on(&["sample_project::policy_management::repository", "sample_project::policy_management::model"])
4545

4646
.build();
4747

@@ -59,13 +59,13 @@ fn test_three_tier_architecture() {
5959
#[rustfmt::skip]
6060
let rules = ArchitecturalRules::define()
6161
.rules_for_module("sample_project::conversion::application")
62-
.may_depend_on(&["sample_project::conversion::domain", "sample_project::contract"])
62+
.it_may_depend_on(&["sample_project::conversion::domain", "sample_project::contract"])
6363

6464
.rules_for_module("sample_project::conversion::domain")
65-
.must_not_depend_on_anything()
65+
.it_must_not_depend_on_anything()
6666

6767
.rules_for_module("sample_project::conversion::infrastructure")
68-
.may_depend_on(&["sample_project::conversion::domain", "sample_project::conversion::application"])
68+
.it_may_depend_on(&["sample_project::conversion::domain", "sample_project::conversion::application"])
6969

7070
.build();
7171

examples/workspace_project/tests/src/architecture_rules_test.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ fn test_vertical_slices_architecture_rules() {
99
#[rustfmt::skip]
1010
let rules = ArchitecturalRules::define()
1111
.rules_for_crate("contracts")
12-
.must_not_depend_on_anything()
12+
.it_must_not_depend_on_anything()
1313

1414
.rules_for_crate("conversion")
15-
.may_depend_on(&["contracts"])
15+
.it_may_depend_on(&["contracts"])
1616

1717
.rules_for_crate("policy_management")
18-
.may_depend_on(&["contracts"])
18+
.it_may_depend_on(&["contracts"])
1919

2020
.rules_for_crate("application")
21-
.may_depend_on(&["conversion", "policy_management"])
21+
.it_may_depend_on(&["conversion", "policy_management"])
2222

2323
.build();
2424

@@ -38,13 +38,13 @@ fn test_mvc_architecture_rules() {
3838
#[rustfmt::skip]
3939
let rules = ArchitecturalRules::define()
4040
.rules_for_module("crate::policy_management::model")
41-
.must_not_depend_on_anything()
41+
.it_must_not_depend_on_anything()
4242

4343
.rules_for_module("crate::policy_management::repository")
44-
.may_depend_on(&["crate::policy_management::model"])
44+
.it_may_depend_on(&["crate::policy_management::model"])
4545

4646
.rules_for_module("crate::policy_management::controller")
47-
.may_depend_on(&["crate::policy_management::repository", "crate::policy_management::model"])
47+
.it_may_depend_on(&["crate::policy_management::repository", "crate::policy_management::model"])
4848

4949
.build();
5050

@@ -62,13 +62,13 @@ fn test_three_tier_architecture() {
6262
#[rustfmt::skip]
6363
let rules = ArchitecturalRules::define()
6464
.rules_for_module("crate::conversion::application")
65-
.may_depend_on(&["crate::conversion::domain"])
65+
.it_may_depend_on(&["crate::conversion::domain"])
6666

6767
.rules_for_module("crate::conversion::domain")
68-
.must_not_depend_on_anything()
68+
.it_must_not_depend_on_anything()
6969

7070
.rules_for_module("crate::conversion::infrastructure")
71-
.may_depend_on(&["crate::conversion::domain", "crate::conversion::application"])
71+
.it_may_depend_on(&["crate::conversion::domain", "crate::conversion::application"])
7272
.build();
7373

7474
let result = Arkitect::ensure_that(project).complies_with(rules);

src/dependency_parsing.rs

Lines changed: 23 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
1+
use crate::rules::rule::RustFile;
12
use std::collections::HashSet;
23
use std::fs;
34
use std::ops::Deref;
4-
use std::path::Path;
55
use syn::{File, Item, ItemUse, UseTree};
6-
use toml::Value;
76

8-
pub fn get_dependencies_in_file(path: &str) -> Vec<String> {
9-
let content = match fs::read_to_string(path) {
7+
pub fn get_dependencies_in_file(file: &RustFile) -> Vec<String> {
8+
let content = match fs::read_to_string(&file.path) {
109
Ok(content) => content,
11-
Err(e) => panic!("Failed to read file file://{}: {}", path, e),
10+
Err(e) => panic!("Failed to read file file://{}: {}", file.path, e),
1211
};
1312

1413
let ast = match syn::parse_file(&content) {
1514
Ok(ast) => ast,
16-
Err(e) => panic!("Failed to parse file file://{}: {}", path, e),
15+
Err(e) => panic!("Failed to parse file file://{}: {}", file.path, e),
1716
};
1817

19-
match get_module(path) {
20-
Ok(module) => get_dependencies_in_ast(&ast, &module),
21-
Err(_e) => vec![],
22-
}
18+
get_dependencies_in_ast(&ast, &file.logical_path)
2319
}
2420

2521
fn parse_module_item(item: &Item, dependencies: &mut Vec<String>, current_module: &str) {
@@ -48,17 +44,22 @@ fn get_dependencies_in_str(s: &str, module: &str) -> Vec<String> {
4844
get_dependencies_in_ast(&ast, module)
4945
}
5046

51-
pub fn get_dependencies_in_ast(ast: &File, current_module: &str) -> Vec<String> {
47+
pub fn get_dependencies_in_ast(ast: &File, current_module_logical_path: &str) -> Vec<String> {
5248
let mut dependencies = Vec::new();
5349

5450
for item in ast.items.iter() {
5551
match item {
5652
Item::Use(ItemUse { tree, .. }) => {
57-
collect_dependencies_from_tree(tree, &mut dependencies, current_module, "");
53+
collect_dependencies_from_tree(
54+
tree,
55+
&mut dependencies,
56+
current_module_logical_path,
57+
"",
58+
);
5859
}
5960
Item::Mod(mod_item) => {
6061
if let Some((_, items)) = &mod_item.content {
61-
let module = format!("{}::{}", current_module, mod_item.ident);
62+
let module = format!("{}::{}", current_module_logical_path, mod_item.ident);
6263
for sub_item in items.iter() {
6364
parse_module_item(sub_item, &mut dependencies, &module);
6465
}
@@ -139,115 +140,15 @@ fn collect_dependencies_from_tree(
139140
}
140141
}
141142

142-
pub fn get_module(file_path: &str) -> Result<String, String> {
143-
let path = Path::new(file_path);
144-
145-
if path.is_dir() {
146-
return Err(format!(
147-
"The specified path '{}' is a directory, not a file",
148-
file_path
149-
));
150-
}
151-
152-
if path.extension().and_then(|ext| ext.to_str()) != Some("rs") {
153-
return Err(format!(
154-
"Invalid file type: expected a Rust file (.rs), found '{}'",
155-
path.extension()
156-
.and_then(|ext| ext.to_str())
157-
.unwrap_or("unknown")
158-
));
159-
}
160-
161-
let crate_root = path
162-
.ancestors()
163-
.find(|ancestor| ancestor.join("Cargo.toml").exists())
164-
.ok_or_else(|| format!("File is not part of a Rust crate: {}", file_path))?;
165-
166-
let cargo_toml_path = crate_root.join("Cargo.toml");
167-
let cargo_toml_content = std::fs::read_to_string(&cargo_toml_path).map_err(|_| {
168-
format!(
169-
"Failed to read Cargo.toml in '{}'",
170-
cargo_toml_path.display()
171-
)
172-
})?;
173-
let crate_name = toml::from_str::<Value>(&cargo_toml_content)
174-
.and_then(|parsed| {
175-
parsed
176-
.get("package")
177-
.and_then(|pkg| pkg.get("name"))
178-
.and_then(|name| name.as_str())
179-
.map(str::to_string)
180-
.ok_or_else(|| serde::de::Error::custom("Missing 'package.name' in Cargo.toml"))
181-
})
182-
.map_err(|err| format!("Failed to parse crate name: {}", err))?;
183-
184-
let relative_path = path.strip_prefix(crate_root).map_err(|_| {
185-
format!(
186-
"Failed to compute relative path for file '{}' in crate '{}'",
187-
file_path,
188-
crate_root.display()
189-
)
190-
})?;
191-
192-
let mut comps = relative_path.components().peekable();
193-
194-
if comps.clone().any(|c| c.as_os_str() == "src") {
195-
while let Some(c) = comps.next() {
196-
if c.as_os_str() == "src" {
197-
break;
198-
}
199-
}
200-
}
201-
202-
let mut parts = vec![];
203-
for comp in comps {
204-
let s = comp.as_os_str().to_str().unwrap_or_default();
205-
parts.push(s.to_string());
206-
}
207-
208-
if let Some(last) = parts.last_mut() {
209-
if last.ends_with(".rs") {
210-
*last = last.trim_end_matches(".rs").to_string();
211-
}
212-
}
213-
214-
if parts.is_empty() {
215-
return Err(format!(
216-
"Failed to determine module path for '{}'",
217-
file_path
218-
));
219-
}
220-
221-
let module_path = parts.join("::");
222-
Ok(format!("{}::{}", crate_name, module_path))
223-
}
224-
225143
#[cfg(test)]
226144
mod tests {
227145
use super::*;
228146

229-
#[test]
230-
fn test_get_module() {
231-
let module =
232-
get_module("./examples/workspace_project/conversion/src/application.rs").unwrap();
233-
234-
assert_eq!(module, "conversion::application")
235-
}
236-
237-
#[test]
238-
fn test_get_module_on_a_random_file() {
239-
let module = get_module("./examples/workspace_project/assets/file_1.txt");
240-
241-
assert_eq!(
242-
module,
243-
Err("Invalid file type: expected a Rust file (.rs), found 'txt'".to_string())
244-
);
245-
}
246-
247147
#[test]
248148
pub fn test_parsing() {
249-
let dependencies =
250-
get_dependencies_in_file("./examples/sample_project/src/conversion/application.rs");
149+
let dependencies = get_dependencies_in_file(&RustFile::from(
150+
"./examples/sample_project/src/conversion/application.rs",
151+
));
251152
assert_eq!(
252153
dependencies,
253154
vec![
@@ -260,8 +161,9 @@ mod tests {
260161

261162
#[test]
262163
pub fn test_workspace_parsing() {
263-
let dependencies =
264-
get_dependencies_in_file("./examples/workspace_project/conversion/src/application.rs");
164+
let dependencies = get_dependencies_in_file(&RustFile::from(
165+
"./examples/workspace_project/conversion/src/application.rs",
166+
));
265167
assert_eq!(
266168
dependencies,
267169
vec![
@@ -271,16 +173,6 @@ mod tests {
271173
);
272174
}
273175

274-
#[test]
275-
fn test_get_module_on_a_directory() {
276-
assert_eq!(
277-
get_module("./examples/workspace_project/"),
278-
Err(String::from(
279-
"The specified path './examples/workspace_project/' is a directory, not a file"
280-
))
281-
);
282-
}
283-
284176
#[test]
285177
fn test_dependencies() {
286178
let source = r#"
@@ -374,7 +266,9 @@ mod tests {
374266
#[test]
375267
fn test_super_dependencies() {
376268
assert_eq!(
377-
get_dependencies_in_file("./examples/sample_project/src/conversion/infrastructure.rs"),
269+
get_dependencies_in_file(&RustFile::from(
270+
"./examples/sample_project/src/conversion/infrastructure.rs"
271+
)),
378272
vec![String::from(
379273
"sample_project::conversion::application::application_function"
380274
)]
@@ -500,11 +394,4 @@ mod tests {
500394

501395
assert_eq!(dependencies, expected_dependencies);
502396
}
503-
504-
#[test]
505-
fn test_get_module_with_a_file_in_folder_without_src() {
506-
let module = get_module("tests/test_architecture.rs");
507-
508-
assert_eq!("rust_arkitect::tests::test_architecture", module.unwrap());
509-
}
510397
}

0 commit comments

Comments
 (0)