archunit/slices/fluentapi/
slice_scope_builder.rsuse std::path::Path;
use crate::{
common::{
ArchUnitError, CheckOptions, ProjectLocator, extract_graph_with_options,
locate_project_from,
},
slices::{
PlantUmlRenderer, SliceProjection, SliceProjectionError, export_plantuml_report,
slice_by_pattern, slice_by_regex, slice_identity,
},
};
use super::{
NegativeSliceConditionBuilder, PositiveSliceConditionBuilder, SliceConfigurationError,
};
#[derive(Debug, Clone)]
#[must_use = "a slice definition has no effect until it receives a rule terminal"]
pub struct SliceScopeBuilder {
project_locator: ProjectLocator,
projection: SliceProjection,
configuration_error: Option<SliceConfigurationError>,
}
impl SliceScopeBuilder {
pub(super) fn new(project_locator: ProjectLocator) -> Self {
Self {
project_locator,
projection: slice_identity(),
configuration_error: None,
}
}
pub fn defined_by(mut self, pattern: impl Into<crate::common::PatternSpec>) -> Self {
self.set_projection(slice_by_pattern(pattern), "pattern");
self
}
pub fn defined_by_regex(mut self, expression: impl Into<crate::common::PatternSpec>) -> Self {
self.set_projection(slice_by_regex(expression), "regular-expression");
self
}
pub fn with_projection(mut self, projection: SliceProjection) -> Self {
if self.configuration_error.is_none() {
self.projection = projection;
}
self
}
pub fn should_not(self) -> NegativeSliceConditionBuilder {
NegativeSliceConditionBuilder::new(self)
}
pub fn should(self) -> PositiveSliceConditionBuilder {
PositiveSliceConditionBuilder::new(self)
}
pub fn to_plantuml(&self) -> Result<String, ArchUnitError> {
self.to_plantuml_with(&CheckOptions::default())
}
pub fn to_plantuml_with(&self, options: &CheckOptions) -> Result<String, ArchUnitError> {
self.validate_configuration()?;
let project = locate_project_from(self.project_locator())?;
let extraction = extract_graph_with_options(&project, options)?;
let graph = extraction.graph();
let edges = self.projection().project(graph);
let components = self.projection().slice_labels(graph);
PlantUmlRenderer::render_with_components(&edges, &components).map_err(|source| {
crate::common::UserError::with_source(
"the generated PlantUML diagram is invalid",
source,
)
.into()
})
}
pub fn export_as_plantuml(&self, output_path: impl AsRef<Path>) -> Result<(), ArchUnitError> {
self.export_as_plantuml_with(output_path, &CheckOptions::default())
}
pub fn export_as_plantuml_with(
&self,
output_path: impl AsRef<Path>,
options: &CheckOptions,
) -> Result<(), ArchUnitError> {
let content = self.to_plantuml_with(options)?;
export_plantuml_report(output_path, &content)
}
#[must_use]
pub const fn project_locator(&self) -> &ProjectLocator {
&self.project_locator
}
#[must_use]
pub const fn projection(&self) -> &SliceProjection {
&self.projection
}
pub(super) const fn configuration_error(&self) -> Option<&SliceConfigurationError> {
self.configuration_error.as_ref()
}
fn validate_configuration(&self) -> Result<(), ArchUnitError> {
if let Some(error) = self.configuration_error() {
Err(error.to_archunit_error())
} else {
Ok(())
}
}
fn set_projection(
&mut self,
projection: Result<SliceProjection, SliceProjectionError>,
context: &'static str,
) {
if self.configuration_error.is_some() {
return;
}
match projection {
Ok(projection) => self.projection = projection,
Err(source) => {
self.configuration_error =
Some(SliceConfigurationError::InvalidProjection { context, source });
}
}
}
}
#[cfg(test)]
mod tests {
use crate::slices::{project_slices, slice_by_file_suffix};
#[test]
fn definitions_are_consuming_branchable_values() {
let base = project_slices();
let pattern = base.clone().defined_by("src/(**)/");
let regex = base.clone().defined_by_regex(r"\Asrc/([^/]+)/");
let suffix = base.clone().with_projection(
slice_by_file_suffix([("_service", "services")])
.expect("fixture suffix should be valid"),
);
assert_eq!(
base.projection().label_for("src/api/mod.rs"),
Some("src/api/mod.rs".to_owned())
);
assert_eq!(
pattern.projection().label_for("src/api/mod.rs"),
Some("api".to_owned())
);
assert_eq!(
regex.projection().label_for("src/domain/mod.rs"),
Some("domain".to_owned())
);
assert_eq!(
suffix.projection().label_for("src/order_service.rs"),
Some("services".to_owned())
);
assert!(base.project_locator().path().is_none());
}
}