Skip to content

Commit

Permalink
Add Spring properties customizer
Browse files Browse the repository at this point in the history
Closes gh-63
  • Loading branch information
mnhock committed Jul 6, 2024
1 parent 0584083 commit bdf6d9f
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 23 deletions.
59 changes: 39 additions & 20 deletions docs/USERGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,26 +157,30 @@ The default mode is `ONLY_TESTS`, which checks only test classes.

The default mode is `WITHOUT_TESTS`, which excludes test classes from the import check.

| Category | Method Name | Rule Description |
|----------------|--------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| General | `noAutowiredFields` | Fields should not be annotated with `@Autowired`, prefer constructor injection |
| Boot | `springBootApplicationShouldBeIn` | Ensure `@SpringBootApplication` is in the default package |
| Configurations | `namesShouldEndWithConfiguration` | Configuration annotated with `@Configuration` classes should end with `Configuration` |
| Configurations | `namesShouldMatch` | Configuration annotated with `@Configuration` classes should match a regex pattern |
| Controllers | `namesShouldEndWithController` | Controllers annotated with `@Controller` or `@RestController` should end with `Controller` |
| Controllers | `namesShouldMatch` | Controllers annotated with `@Controller` or `@RestController` should match a regex pattern |
| Controllers | `shouldBeAnnotatedWithController` | Controllers should be annotated with `@Controller` |
| Controllers | `shouldBeAnnotatedWithRestController` | Controllers should be annotated with `@RestController` |
| Controllers | `shouldBePackagePrivate` | Controllers annotated with `@Controller` or `@RestController` should be package-private |
| Controllers | `shouldNotDependOnOtherControllers` | Controllers annotated with `@Controller` or `@RestController` should not depend on other controllers annotated with `@Controller` or `@RestController` |
| Repositories | `namesShouldEndWithRepository` | Repositories annotated with `@Repository` should end with `Repository` |
| Repositories | `namesShouldMatch` | Repositories annotated with `@Repository` should match a regex pattern |
| Repositories | `shouldBeAnnotatedWithRepository` | Repositories should be annotated with `@Repository` |
| Repositories | `shouldNotDependOnServices` | Repositories annotated with `@Repository` should not depend on service classes annotated with `@Service.` |
| Services | `namesShouldEndWithService` | Services annotated with `@Service.` should end with `Service` |
| Services | `namesShouldMatch` | Services annotated with `@Service.` should match a regex pattern |
| Services | `shouldBeAnnotatedWithService` | Services should be annotated with `@Service` |
| Services | `shouldNotDependOnControllers` | Services annotated with `@Service.` should not depend on controllers annotated with `@Controller` or `@RestController` |
| Category | Method Name | Rule Description |
|----------------|------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| General | `noAutowiredFields` | Fields should not be annotated with `@Autowired`, prefer constructor injection |
| Boot | `springBootApplicationShouldBeIn` | Ensure `@SpringBootApplication` is in the default package |
| Properties | `namesShouldEndWithProperties` | Properties annotated with `@ConfigurationProperties` should end with `Properties` |
| Properties | `namesShouldMatch` | Properties annotated with `@ConfigurationProperties` should match a regex pattern |
| Properties | `shouldBeAnnotatedWithValidated` | Properties annotated with `@ConfigurationProperties` should be annotated with `@Validated` |
| Properties | `shouldBeAnnotatedWithConfigurationProperties` | Properties should be annotated with `@ConfigurationProperties` |
| Configurations | `namesShouldEndWithConfiguration` | Configuration annotated with `@Configuration` classes should end with `Configuration` |
| Configurations | `namesShouldMatch` | Configuration annotated with `@Configuration` classes should match a regex pattern |
| Controllers | `namesShouldEndWithController` | Controllers annotated with `@Controller` or `@RestController` should end with `Controller` |
| Controllers | `namesShouldMatch` | Controllers annotated with `@Controller` or `@RestController` should match a regex pattern |
| Controllers | `shouldBeAnnotatedWithController` | Controllers should be annotated with `@Controller` |
| Controllers | `shouldBeAnnotatedWithRestController` | Controllers should be annotated with `@RestController` |
| Controllers | `shouldBePackagePrivate` | Controllers annotated with `@Controller` or `@RestController` should be package-private |
| Controllers | `shouldNotDependOnOtherControllers` | Controllers annotated with `@Controller` or `@RestController` should not depend on other controllers annotated with `@Controller` or `@RestController` |
| Repositories | `namesShouldEndWithRepository` | Repositories annotated with `@Repository` should end with `Repository` |
| Repositories | `namesShouldMatch` | Repositories annotated with `@Repository` should match a regex pattern |
| Repositories | `shouldBeAnnotatedWithRepository` | Repositories should be annotated with `@Repository` |
| Repositories | `shouldNotDependOnServices` | Repositories annotated with `@Repository` should not depend on service classes annotated with `@Service.` |
| Services | `namesShouldEndWithService` | Services annotated with `@Service.` should end with `Service` |
| Services | `namesShouldMatch` | Services annotated with `@Service.` should match a regex pattern |
| Services | `shouldBeAnnotatedWithService` | Services should be annotated with `@Service` |
| Services | `shouldNotDependOnControllers` | Services annotated with `@Service.` should not depend on controllers annotated with `@Controller` or `@RestController` |

## 5. Java Rules

Expand Down Expand Up @@ -459,6 +463,21 @@ Taikai.builder()
.check();
```

- **Properties Configuration**: Ensure that configuration property classes end with `Properties` or match a specific regex pattern, are annotated with `@ConfigurationProperties` or annotated with `@Validated`.

```java
Taikai.builder()
.namespace("com.company.yourproject")
.spring(spring -> spring
.properties(properties -> properties
.shouldBeAnnotatedWithConfigurationProperties()
.namesShouldEndWithProperties()
.namesShouldMatch("regex")
.shouldBeAnnotatedWithValidated()))
.build()
.check();
```

- **Configurations Configuration**: Ensure that configuration classes end with `Configuration` or match a specific regex pattern.

```java
Expand Down
78 changes: 78 additions & 0 deletions src/main/java/com/enofex/taikai/spring/PropertiesConfigurer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.enofex.taikai.spring;

import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_CONFIGURATION_PROPERTIES;
import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_VALIDATED;
import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithConfigurationProperties;
import static com.tngtech.archunit.lang.conditions.ArchConditions.be;
import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

import com.enofex.taikai.TaikaiRule;
import com.enofex.taikai.TaikaiRule.Configuration;
import com.enofex.taikai.configures.AbstractConfigurer;
import com.enofex.taikai.configures.ConfigurerContext;

public final class PropertiesConfigurer extends AbstractConfigurer {

private static final String DEFAULT_PROPERTIES_NAME_MATCHING = ".+Properties";

PropertiesConfigurer(ConfigurerContext configurerContext) {
super(configurerContext);
}

public PropertiesConfigurer namesShouldEndWithProperties() {
return namesShouldMatch(DEFAULT_PROPERTIES_NAME_MATCHING, null);
}

public PropertiesConfigurer namesShouldEndWithProperties(Configuration configuration) {
return namesShouldMatch(DEFAULT_PROPERTIES_NAME_MATCHING, configuration);
}

public PropertiesConfigurer namesShouldMatch(String regex) {
return namesShouldMatch(regex, null);
}

public PropertiesConfigurer namesShouldMatch(String regex, Configuration configuration) {
return addRule(TaikaiRule.of(classes()
.that(are(annotatedWithConfigurationProperties(true)))
.should().haveNameMatching(regex)
.as("Properties should have name ending %s".formatted(regex)), configuration));
}

public PropertiesConfigurer shouldBeAnnotatedWithValidated() {
return shouldBeAnnotatedWithValidated(null);
}

public PropertiesConfigurer shouldBeAnnotatedWithValidated(Configuration configuration) {
return addRule(TaikaiRule.of(classes()
.that(are(annotatedWithConfigurationProperties(true)))
.should().beMetaAnnotatedWith(ANNOTATION_VALIDATED)
.as("Configuration properties annotated with %s should be annotated with %s as well".formatted(
ANNOTATION_VALIDATED)),
configuration));
}

public PropertiesConfigurer shouldBeAnnotatedWithConfigurationProperties() {
return shouldBeAnnotatedWithConfigurationProperties(DEFAULT_PROPERTIES_NAME_MATCHING, null);
}

public PropertiesConfigurer shouldBeAnnotatedWithConfigurationProperties(
Configuration configuration) {
return shouldBeAnnotatedWithConfigurationProperties(DEFAULT_PROPERTIES_NAME_MATCHING,
configuration);
}

public PropertiesConfigurer shouldBeAnnotatedWithConfigurationProperties(String regex) {
return shouldBeAnnotatedWithConfigurationProperties(regex, null);
}

public PropertiesConfigurer shouldBeAnnotatedWithConfigurationProperties(String regex,
Configuration configuration) {
return addRule(TaikaiRule.of(classes()
.that().haveNameMatching(regex)
.should(be(annotatedWithConfigurationProperties(true)))
.as("Configuration properties should be annotated with %s".formatted(
ANNOTATION_CONFIGURATION_PROPERTIES)),
configuration));
}
}
9 changes: 7 additions & 2 deletions src/main/java/com/enofex/taikai/spring/SpringConfigurer.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.enofex.taikai.spring;

import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_AUTOWIRED;
import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedAutowired;
import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithAutowired;
import static com.tngtech.archunit.lang.conditions.ArchConditions.be;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noFields;

Expand All @@ -17,6 +17,10 @@ public SpringConfigurer(ConfigurerContext configurerContext) {
super(configurerContext);
}

public SpringConfigurer properties(Customizer<PropertiesConfigurer> customizer) {
return customizer(customizer, () -> new PropertiesConfigurer(configurerContext()));
}

public SpringConfigurer configurations(Customizer<ConfigurationsConfigurer> customizer) {
return customizer(customizer, () -> new ConfigurationsConfigurer(configurerContext()));
}
Expand All @@ -43,13 +47,14 @@ public SpringConfigurer noAutowiredFields() {

public SpringConfigurer noAutowiredFields(Configuration configuration) {
return addRule(TaikaiRule.of(noFields()
.should(be(annotatedAutowired(true)))
.should(be(annotatedWithAutowired(true)))
.as("No fields should be annotated with %s, use constructor injection".formatted(
ANNOTATION_AUTOWIRED)), configuration));
}

@Override
public void disable() {
disable(PropertiesConfigurer.class);
disable(ConfigurationsConfigurer.class);
disable(ControllersConfigurer.class);
disable(ServicesConfigurer.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
final class SpringDescribedPredicates {

static final String ANNOTATION_CONFIGURATION = "org.springframework.context.annotation.Configuration";
static final String ANNOTATION_CONFIGURATION_PROPERTIES = "org.springframework.boot.context.properties.ConfigurationProperties";
static final String ANNOTATION_CONTROLLER = "org.springframework.web.bind.annotation.Controller";
static final String ANNOTATION_REST_CONTROLLER = "org.springframework.web.bind.annotation.RestController";
static final String ANNOTATION_SERVICE = "org.springframework.stereotype.Service";
static final String ANNOTATION_REPOSITORY = "org.springframework.stereotype.Repository";
static final String ANNOTATION_SPRING_BOOT_APPLICATION = "org.springframework.boot.autoconfigure.SpringBootApplication";
static final String ANNOTATION_AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired";
static final String ANNOTATION_VALIDATED = "org.springframework.validation.annotation.Validated";

private SpringDescribedPredicates() {
}
Expand All @@ -30,6 +32,11 @@ static DescribedPredicate<CanBeAnnotated> annotatedWithConfiguration(
return annotatedWith(ANNOTATION_CONFIGURATION, isMetaAnnotated);
}

static DescribedPredicate<CanBeAnnotated> annotatedWithConfigurationProperties(
boolean isMetaAnnotated) {
return annotatedWith(ANNOTATION_CONFIGURATION_PROPERTIES, isMetaAnnotated);
}

static DescribedPredicate<CanBeAnnotated> annotatedWithRestController(boolean isMetaAnnotated) {
return annotatedWith(ANNOTATION_REST_CONTROLLER, isMetaAnnotated);
}
Expand All @@ -51,7 +58,11 @@ static DescribedPredicate<CanBeAnnotated> annotatedWithSpringBootApplication(
return annotatedWith(ANNOTATION_SPRING_BOOT_APPLICATION, isMetaAnnotated);
}

static DescribedPredicate<CanBeAnnotated> annotatedAutowired(boolean isMetaAnnotated) {
static DescribedPredicate<CanBeAnnotated> annotatedWithAutowired(boolean isMetaAnnotated) {
return annotatedWith(ANNOTATION_AUTOWIRED, isMetaAnnotated);
}

static DescribedPredicate<CanBeAnnotated> annotatedWithValidated(boolean isMetaAnnotated) {
return annotatedWith(ANNOTATION_VALIDATED, isMetaAnnotated);
}
}
5 changes: 5 additions & 0 deletions src/test/java/com/enofex/taikai/Usage.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public static void main(String[] args) {
.noAutowiredFields()
.boot(boot -> boot
.springBootApplicationShouldBeIn("com.enofex.taikai"))
.properties(properties -> properties
.namesShouldEndWithProperties()
.namesShouldMatch("regex")
.shouldBeAnnotatedWithConfigurationProperties()
.shouldBeAnnotatedWithValidated())
.configurations(configuration -> configuration
.namesShouldEndWithConfiguration()
.namesShouldMatch("regex"))
Expand Down

0 comments on commit bdf6d9f

Please sign in to comment.