-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Updating layer validations #183
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,40 +35,43 @@ impl Layers { | |
} | ||
} | ||
|
||
impl Checker { | ||
fn validate_pack(&self, pack: &Pack) -> Option<String> { | ||
match &pack.layer { | ||
Some(layer) => { | ||
if self.layers.layers.contains(layer) { | ||
None | ||
} else { | ||
Some(format!( | ||
"Invalid 'layer' option in '{}'. `layer` must be one of the layers defined in `packwerk.yml`", | ||
&pack.relative_yml().to_string_lossy() | ||
)) | ||
} | ||
} | ||
None => match &pack.enforce_architecture { | ||
Some(setting) => { | ||
if setting.is_false() { | ||
None | ||
} else { | ||
Some(format!( | ||
"'layer' must be specified in '{}' because `enforce_architecture` is true or strict.", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes sense... if a pack enforces architecture, it must specify a layer. Surprised we didn't have this before! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Huh yeah, I double checked and it definitely just let it fly before! |
||
pack.relative_yml().to_string_lossy() | ||
)) | ||
} | ||
} | ||
None => None, | ||
}, | ||
} | ||
} | ||
} | ||
|
||
impl ValidatorInterface for Checker { | ||
fn validate(&self, configuration: &Configuration) -> Option<Vec<String>> { | ||
let mut error_messages: Vec<String> = vec![]; | ||
match configuration.pack_set.all_pack_dependencies(configuration) { | ||
Ok(dependencies) => { | ||
for pack_dependency in dependencies { | ||
let (from_pack, to_pack) = | ||
(pack_dependency.from_pack, pack_dependency.to_pack); | ||
match dependency_permitted( | ||
configuration, | ||
from_pack, | ||
to_pack, | ||
) { | ||
Ok(true) => continue, | ||
Ok(false) => { | ||
let error_message = format!( | ||
"Invalid 'dependencies' in '{}/package.yml'. '{}/package.yml' has a layer type of '{},' which cannot rely on '{},' which has a layer type of '{}.' `architecture_layers` can be found in packwerk.yml", | ||
from_pack.relative_path.display(), | ||
from_pack.relative_path.display(), | ||
from_pack.layer.clone().unwrap(), | ||
to_pack.name, | ||
to_pack.layer.clone().unwrap(), | ||
); | ||
error_messages.push(error_message); | ||
} | ||
Err(error) => { | ||
error_messages.push(error.to_string()); | ||
return Some(error_messages); | ||
} | ||
} | ||
} | ||
} | ||
Err(error) => { | ||
error_messages.push(error.to_string()); | ||
|
||
for pack in &configuration.pack_set.packs { | ||
if let Some(error_message) = self.validate_pack(pack) { | ||
error_messages.push(error_message); | ||
} | ||
} | ||
|
||
|
@@ -80,31 +83,6 @@ impl ValidatorInterface for Checker { | |
} | ||
} | ||
|
||
fn dependency_permitted( | ||
configuration: &Configuration, | ||
from_pack: &Pack, | ||
to_pack: &Pack, | ||
) -> Result<bool> { | ||
if from_pack.enforce_architecture().is_false() { | ||
return Ok(true); | ||
} | ||
|
||
let (from_pack_layer, to_pack_layer) = (&from_pack.layer, &to_pack.layer); | ||
|
||
if from_pack_layer.is_none() || to_pack_layer.is_none() { | ||
return Ok(true); | ||
} | ||
|
||
let (from_pack_layer, to_pack_layer) = ( | ||
from_pack_layer.as_ref().unwrap(), | ||
to_pack_layer.as_ref().unwrap(), | ||
); | ||
|
||
configuration | ||
.layers | ||
.can_depend_on(from_pack_layer, to_pack_layer) | ||
} | ||
|
||
pub struct Checker { | ||
pub layers: Layers, | ||
} | ||
|
@@ -367,89 +345,139 @@ mod tests { | |
} | ||
} | ||
} | ||
fn package_yml_architecture_test(test_case: ArchitectureTestCase) { | ||
|
||
fn validate_layers( | ||
config_layers: Vec<String>, | ||
package_layer: Option<String>, | ||
package_enforce_layer: Option<CheckerSetting>, | ||
) -> Option<Vec<String>> { | ||
let root_pack = Pack { | ||
name: String::from("."), | ||
layer: None, | ||
..Pack::default() | ||
}; | ||
|
||
let from_pack = Pack { | ||
name: test_case.from_pack_name, | ||
layer: test_case.from_pack_layer, | ||
enforce_architecture: test_case.from_pack_enforce_architecture, | ||
dependencies: test_case.from_pack_dependencies, | ||
let test_pack = Pack { | ||
name: String::from("packs/foo"), | ||
relative_path: PathBuf::from("packs/foo/package.yml"), | ||
layer: package_layer, | ||
enforce_architecture: package_enforce_layer, | ||
..Pack::default() | ||
}; | ||
let to_pack = Pack { | ||
name: test_case.to_pack_name, | ||
layer: test_case.to_pack_layer, | ||
..Pack::default() | ||
}; | ||
|
||
let configuration = Configuration { | ||
pack_set: PackSet::build( | ||
HashSet::from_iter(vec![ | ||
root_pack, | ||
from_pack.clone(), | ||
to_pack.clone(), | ||
]), | ||
HashSet::from_iter(vec![root_pack, test_pack]), | ||
HashMap::new(), | ||
) | ||
.unwrap(), | ||
..Configuration::default() | ||
}; | ||
let checker = Checker { | ||
layers: Layers { | ||
layers: test_case.layers, | ||
layers: config_layers, | ||
}, | ||
..Configuration::default() | ||
}; | ||
|
||
let result = dependency_permitted(&configuration, &from_pack, &to_pack); | ||
assert_eq!(result.unwrap(), test_case.expected_result); | ||
checker.validate(&configuration) | ||
} | ||
|
||
#[test] | ||
fn package_yml_dependency_not_permitted() { | ||
let test_case = ArchitectureTestCase::default(); | ||
package_yml_architecture_test(test_case); | ||
fn validate_layers_strict_true() { | ||
let result = validate_layers( | ||
vec![String::from("product"), String::from("utilities")], | ||
Some(String::from("product")), | ||
Some(CheckerSetting::Strict), | ||
); | ||
assert_eq!(result, None); | ||
|
||
let result = validate_layers( | ||
vec![String::from("product"), String::from("utilities")], | ||
Some(String::from("product")), | ||
Some(CheckerSetting::True), | ||
); | ||
assert_eq!(result, None); | ||
} | ||
|
||
#[test] | ||
fn package_yml_dependency_permitted_violation_not_enforced() { | ||
let test_case = ArchitectureTestCase { | ||
from_pack_enforce_architecture: Some(CheckerSetting::False), | ||
expected_result: true, | ||
..ArchitectureTestCase::default() | ||
}; | ||
package_yml_architecture_test(test_case); | ||
fn validate_layers_false_none() { | ||
let result = validate_layers( | ||
vec![String::from("product"), String::from("utilities")], | ||
None, | ||
Some(CheckerSetting::False), | ||
); | ||
assert_eq!(result, None); | ||
|
||
let result = validate_layers( | ||
vec![String::from("product"), String::from("utilities")], | ||
None, | ||
None, | ||
); | ||
assert_eq!(result, None); | ||
} | ||
|
||
#[test] | ||
fn package_yml_dependency_permitted_violation_no_from_layer() { | ||
let test_case = ArchitectureTestCase { | ||
from_pack_layer: None, | ||
expected_result: true, | ||
..ArchitectureTestCase::default() | ||
}; | ||
package_yml_architecture_test(test_case); | ||
fn validate_layers_false_none_but_layer_specified() { | ||
let result = validate_layers( | ||
vec![String::from("product"), String::from("utilities")], | ||
Some(String::from("product")), | ||
Some(CheckerSetting::False), | ||
); | ||
assert_eq!(result, None); | ||
|
||
let result = validate_layers( | ||
vec![String::from("product"), String::from("utilities")], | ||
Some(String::from("product")), | ||
None, | ||
); | ||
assert_eq!(result, None); | ||
} | ||
|
||
#[test] | ||
fn package_yml_dependency_permitted_violation_no_to_layer() { | ||
let test_case = ArchitectureTestCase { | ||
to_pack_layer: None, | ||
expected_result: true, | ||
..ArchitectureTestCase::default() | ||
}; | ||
package_yml_architecture_test(test_case); | ||
fn validate_layers_true_strict_with_no_layer() { | ||
let result = validate_layers( | ||
vec![String::from("product"), String::from("utilities")], | ||
None, | ||
Some(CheckerSetting::True), | ||
); | ||
assert_eq!(result, Some(vec![String::from("'layer' must be specified in 'packs/foo/package.yml/package.yml' because `enforce_architecture` is true or strict.")])); | ||
|
||
let result = validate_layers( | ||
vec![String::from("product"), String::from("utilities")], | ||
None, | ||
Some(CheckerSetting::Strict), | ||
); | ||
assert_eq!(result, Some(vec![String::from("'layer' must be specified in 'packs/foo/package.yml/package.yml' because `enforce_architecture` is true or strict.")])); | ||
} | ||
|
||
#[test] | ||
fn package_yml_dependency_permitted_violation_valid_layer() { | ||
let test_case = ArchitectureTestCase { | ||
expected_result: true, | ||
layers: vec![String::from("utilities"), String::from("product")], | ||
..ArchitectureTestCase::default() | ||
}; | ||
package_yml_architecture_test(test_case); | ||
fn validate_layers_with_not_found_layer() { | ||
let expected_error = Some(vec![String::from("Invalid 'layer' option in 'packs/foo/package.yml/package.yml'. `layer` must be one of the layers defined in `packwerk.yml`")]); | ||
|
||
let result = validate_layers( | ||
vec![String::from("product"), String::from("utilities")], | ||
Some(String::from("not defined")), | ||
Some(CheckerSetting::True), | ||
); | ||
assert_eq!(result, expected_error); | ||
|
||
let result = validate_layers( | ||
vec![String::from("product"), String::from("utilities")], | ||
Some(String::from("not defined")), | ||
Some(CheckerSetting::Strict), | ||
); | ||
assert_eq!(result, expected_error); | ||
|
||
let result = validate_layers( | ||
vec![String::from("product"), String::from("utilities")], | ||
Some(String::from("not defined")), | ||
Some(CheckerSetting::False), | ||
); | ||
assert_eq!(result, expected_error); | ||
|
||
let result = validate_layers( | ||
vec![String::from("product"), String::from("utilities")], | ||
Some(String::from("not defined")), | ||
None, | ||
); | ||
assert_eq!(result, expected_error); | ||
} | ||
|
||
#[test] | ||
|
@@ -473,10 +501,15 @@ mod tests { | |
}; | ||
|
||
let error = checker.validate(&configuration); | ||
let expected_message = vec![ | ||
String::from("Invalid 'dependencies' in 'packs/baz/package.yml'. 'packs/baz/package.yml' has a layer type of 'technical_services,' which cannot rely on 'packs/bar,' which has a layer type of 'admin.' `architecture_layers` can be found in packwerk.yml"), | ||
String::from( "Invalid 'dependencies' in 'packs/foo/package.yml'. 'packs/foo/package.yml' has a layer type of 'product,' which cannot rely on 'packs/bar,' which has a layer type of 'admin.' `architecture_layers` can be found in packwerk.yml") | ||
assert!(error.is_some()); | ||
let mut errors = error.unwrap(); | ||
errors.sort(); | ||
|
||
let expected_errors = vec![ | ||
"'layer' must be specified in 'packs/baz/package.yml' because `enforce_architecture` is true or strict.".to_string(), | ||
"Invalid 'layer' option in 'packs/bar/package.yml'. `layer` must be one of the layers defined in `packwerk.yml`".to_string(), | ||
"Invalid 'layer' option in 'packs/foo/package.yml'. `layer` must be one of the layers defined in `packwerk.yml`".to_string() | ||
]; | ||
assert_eq!(error, Some(expected_message)); | ||
assert_eq!(errors, expected_errors); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
enforce_privacy: true | ||
enforce_architecture: true | ||
enforce_architecture: false | ||
layer: admin |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
enforce_privacy: true | ||
enforce_architecture: true | ||
layer: technical_services | ||
dependencies: | ||
- packs/bar |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
enforce_dependencies: true | ||
enforce_privacy: true | ||
enforce_architecture: true | ||
layer: product | ||
layer: not_a_layer | ||
dependencies: | ||
- packs/bar |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,12 +35,10 @@ packs/foo, packs/bar", | |
#[test] | ||
fn test_validate_architecture() -> Result<(), Box<dyn Error>> { | ||
let expected_message_1 = String::from( | ||
" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we still want the architecture validator to ensure that stated dependencies do not violate the architecture right? Is your point that if there is a reference that the checker finds, then it's the reference that violates architecture – not the stated dependencies? And we already have that behavior in the system? My only thought here is that even if there are zero references, it's still wrong to have a dependency that conceptually violates a checker that should be restricting that dependency from existing. Can you share more about why you feel like this should be removed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the feedback! There's a few concerns we have about one type of validation using another type's package.yml configuration.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm fine with keeping what you have, but here's what I think:
Ultimately though no big deal... we can try this out and iterate! |
||
Invalid 'dependencies' in 'packs/baz/package.yml'. 'packs/baz/package.yml' has a layer type of 'technical_services,' which cannot rely on 'packs/bar,' which has a layer type of 'admin.' `architecture_layers` can be found in packwerk.yml", | ||
"\'layer\' must be specified in \'packs/baz/package.yml\' because `enforce_architecture` is true or strict.", | ||
); | ||
let expected_message_2 = String::from( | ||
" | ||
Invalid 'dependencies' in 'packs/foo/package.yml'. 'packs/foo/package.yml' has a layer type of 'product,' which cannot rely on 'packs/bar,' which has a layer type of 'admin.' `architecture_layers` can be found in packwerk.yml", | ||
"Invalid \'layer\' option in \'packs/foo/package.yml\'. `layer` must be one of the layers defined in `packwerk.yml`" | ||
); | ||
|
||
Command::cargo_bin("packs") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we do have this right, but it was just a
bail
vs. a first-class error right?(Related to error message:
Could not find one of layer
{}or layer
{}in
packwerk.yml...
)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. The referenced code above is an architecture layer validation message. The "could not..." is a "check" error.