J'ai une annotation @Audit
, elle a de nombreux attributs facultatifs, je dois imposer l'utilisation d'un attribut booléen useAccount = true
pour certains packages.
J'essaie d'utiliser archunit pour accomplir cette validation, de cette façon chaque fois qu'un développeur commet du code qui enfreint la règle, le CI enfreindra et informera l'équipe.
Cela briserait la construction:
methods().that().resideInAnyPackage("..controllers..", "..service..").and().areAnnotatedWith(Audit.class).should(attributeCheckCondition)
C'est la bonne manière:
@Audit(useAccount = true) public myMethod(...) { ... }
Le problème est qu'Archunit ne supporte pas actuellement l'affirmation sur les méthodes. Je m'attendais à faire quelque chose comme:
@Audit public myMethod(...) { ... }
Ensuite, ma condition personnalisée attributeCheckCondition
prendrait soin de rechercher la valeur d'attribut.
Existe-t-il un moyen de récupérer des méthodes lorsque nous récupérons des classes? Sans avoir à écrire un prédicat et une condition plus compliqués?
3 Réponses :
Depuis ArchUnit 0.10.0, il est possible de créer des règles pour les membres.
ArchRule rule = ArchRuleDefinition.all(methods).that(owner(resideInAnyPackage("..controllers..", "..service.."))).and(annotatedWith(Audit.class)).should(haveAttributeValue()); rule.check(javaClasses);
Voir aussi Rédaction des règles de membre dans le guide de l'utilisateur.
Depuis là aucune définition de règle de base n'est actuellement disponible pour les méthodes, une étape intermédiaire est nécessaire. ArchUnit dispose d'un ClassesTransformer
pour transformer JavaClasses en une collection d'autres types.
ClassesTransformer<JavaMethod> methods = new AbstractClassesTransformer<JavaMethod>("methods") { @Override public Iterable<JavaMethod> doTransform(JavaClasses javaClasses) { Set<JavaMethod> allMethods = new HashSet<>(); for (JavaClass javaClass : javaClasses) { allMethods.addAll(javaClass.getMethods()); } return allMethods; } };
Ce ClassesTransformer
peut ensuite être utilisé comme une base pour les définitions de règles personnalisées.
methods().that().areDeclaredInClassesThat().resideInAnyPackage("..controllers..", "..service..").and().areAnnotatedWith(Audit.class).should(attributeCheckCondition)
Voir aussi Règles avec concepts personnalisés dans le Guide de l'utilisateur et ce problème .
J'ai trouvé un moyen de le faire avec un prédicat et une condition personnalisés sur les classes, quand je l'ai fait, je n'étais pas au courant de la réponse de Roland qui semble être meilleure, car elle fournit un moyen d'exprimer l'assertion de règle du point de vue des méthodes qui c'est pourquoi je demandais.
Cependant, je voulais publier la solution ici pour qu'elle puisse être utile pour d'autres.
ArchRule ANNOTATION_RULE = classes() .that() .resideInAnyPackage("..controller..", "..service..") .and(HAVE_A_METHOD_ANNOTATED_WITH_AUDIT) .should(ONLY_SET_ATTRIBUTE_USE_ACCOUNT_SET_TO_TRUE);
Ensuite, la règle est exprimée comme suit:
DescribedPredicate<JavaClass> HAVE_A_METHOD_ANNOTATED_WITH_AUDIT = new DescribedPredicate<JavaClass>("have a method annotated with @Audit") { @Override public boolean apply(JavaClass input) { return input.getMethods().stream().anyMatch(method -> method.isAnnotatedWith(Audit.class)); } }; ArchCondition<JavaClass> ONLY_SET_ATTRIBUTE_USE_ACCOUNT_SET_TO_TRUE = new ArchCondition<JavaClass>("only set useAccount attribute to true") { @Override public void check(JavaClass item, ConditionEvents events) { item.getMethods().stream().filter(method -> method.isAnnotatedWith(Audit.class) && !method.getAnnotationOfType(Audit.class) .useAccount() ) .forEach(method -> { String message = String.format( "Method %s is annotated with @Audit but useAccount is not set to true", method.getFullName()); events.add(SimpleConditionEvent.violated(method, message)); }); } };
Fait amusant: j'ai eu un problème similaire la veille de votre question et je l'ai résolu de la même manière que vous. :RÉ
Voici un autre exemple personnalisé en plus de @raspacorp (qui m'a inspiré!).
Pour vérifier l'annotation de la méthode @Secured (ROLE)
, j'ai implémenté la règle suivante: p>
@ArchTest static ArchRule admin_actions_with_post_mapping_should_be_secured_by_ADMIN_WRITE_role = methods() .that().areDeclaredInClassesThat().resideInAnyPackage(ADMIN_PACKAGES) .and().areAnnotatedWith(PostMapping.class) .should(haveSecuredAnnotationWithRoles("ADMIN_WRITE"));
Voici un exemple d'utilisation de cette archCondition:
public static class SecuredByRoleArchCondition extends ArchCondition<JavaMethod> { private final String[] expectedRoles; public SecuredByRoleArchCondition(String[] expectedRoles) { super(String.format("accessed by @Secured methods with roles %s", Arrays.toString(expectedRoles))); this.expectedRoles = expectedRoles; } public static SecuredByRoleArchCondition haveSecuredAnnotationWithRoles(String... expectedRoles) { return new SecuredByRoleArchCondition(expectedRoles); } @Override public void check(JavaMethod javaMethod, ConditionEvents events) { if (!javaMethod.isAnnotatedWith(Secured.class)) { String message = String.format("Method %s annotation @Secured(%s) is missing", javaMethod.getFullName(), Arrays.toString(expectedRoles)); events.add(SimpleConditionEvent.violated(javaMethod, message)); return; } String[] annotationRoleValues = javaMethod.getAnnotationOfType(Secured.class).value(); if (!Arrays.equals(annotationRoleValues, expectedRoles)) { String message = String.format("Method %s @Secured with %s has wrong roles, expected %s instead", javaMethod.getFullName(), Arrays.toString(annotationRoleValues), Arrays.toString(expectedRoles)); events.add(SimpleConditionEvent.violated(javaMethod, message)); } } }