From b67b47adcd9eaba55c484305e79aa21c003b7589 Mon Sep 17 00:00:00 2001 From: Akash Kansara Date: Sat, 13 Sep 2025 12:59:36 +0530 Subject: [PATCH 1/9] Update README --- README.md | 60 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3945412..85e67d5 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,7 @@ [![License](https://img.shields.io/github/license/akash-kansara/modak)](LICENSE) [![Maven Central](https://img.shields.io/maven-central/v/io.github.akash-kansara/modak-core)](https://search.maven.org/search?q=g:io.github.akash-kansara) -Modak is a companion library to [Jakarta Bean Validation](https://beanvalidation.org/). -**Bean Validation** defines **constraints** and checks whether your object model is valid. -**Modak** focuses only on **data correction**: applying annotation-based rules to automatically fix data. - -If you are already using Bean Validation (Hibernate Validator, Apache BVal, or any implementation) and need automatic correction/sanitization, Modak integrates seamlessly as a drop-in companion. - -It can also be used standalone, without Bean Validation, whenever you need annotation-driven data correction. - -Main features: +Modak is a library to that helps you define data correction rules and provides APIs to correct data in your objects based on those rules. Main features: 1. Lets you express data correction rules on object models via annotations 2. Lets you write custom constraint in an extensible way @@ -118,20 +110,16 @@ public class UserCorrectionApplier implements CorrectionApplier success) { } ``` +## 🔗 Synergy with Bean Validation + +Modak is a companion library to [Jakarta Bean Validation](https://beanvalidation.org/). Its scope is limited to data correction and does not provide data validation features, but it seamlessly integrates with bean validation. +You can use any bean validation such as [Hibernate Validator](https://hibernate.org/validator/), [Apache BVal](https://bval.apache.org/) along with Modak. + +### Bean Validation Example + +```java +public class User { + private String name; + + public User(String name) { + this.name = name; + } + + @NotNull // Jakarta Bean Validation constraint + @DefaultValue( + strValue = "Anonymous", + constraintFilter = {NotNull.class} // Only apply if NotNull constraint fails + ) + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} + +User user = new User(null); +Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); +Set> violations = validator.validate(user); +Corrector corrector = CorrectorFactory.buildCorrector(); +CorrectionResult result = corrector.correct( // Since violations are passed, correction will be applied only if NotNull constraint has failed + user, + violations +); +System.out.println(user.getName()); // Anonymous +``` + ## 📚 Documentation **📖 [Full Documentation](docs/API.md)** From eb58e1996c03198b523801a4dde2474443a950bf Mon Sep 17 00:00:00 2001 From: Akash Kansara Date: Sat, 13 Sep 2025 13:02:27 +0530 Subject: [PATCH 2/9] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 85e67d5..fd29b57 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ User user = new User(null); Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); Set> violations = validator.validate(user); Corrector corrector = CorrectorFactory.buildCorrector(); -CorrectionResult result = corrector.correct( // Since violations are passed, correction will be applied only if NotNull constraint has failed +CorrectionResult result = corrector.correct( // Since violations are supplied, correction will be applied only if NotNull constraint has failed user, violations ); From 3ce9e93b99fc528cccc9af2303881b3fdd3016ec Mon Sep 17 00:00:00 2001 From: Akash Kansara Date: Sat, 13 Sep 2025 13:49:23 +0530 Subject: [PATCH 3/9] Update implementation --- README.md | 36 +----- .../modak/api/CorrectionResult.kt | 10 +- .../akashkansara/modak/api/Corrector.kt | 4 +- .../modak/api/{ErrorLike.kt => Error.kt} | 2 +- core/build.gradle.kts | 2 +- .../akashkansara/modak/core/CorrectorImpl.kt | 11 +- .../models/{ErrorLikeImpl.kt => ErrorImpl.kt} | 6 +- docs/API.md | 110 +++++++++++++----- 8 files changed, 101 insertions(+), 80 deletions(-) rename api/src/main/kotlin/io/github/akashkansara/modak/api/{ErrorLike.kt => Error.kt} (94%) rename core/src/main/kotlin/io/github/akashkansara/modak/core/models/{ErrorLikeImpl.kt => ErrorImpl.kt} (74%) diff --git a/README.md b/README.md index fd29b57..bbe7928 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,6 @@ Modak is a library to that helps you define data correction rules and provides A ### Gradle (Kotlin DSL) ```kotlin dependencies { - // Add only if you're not using bean validation already - implementation("jakarta.validation:jakarta.validation-api:3.1.0") - - // Modak dependencies - implementation("io.github.akash-kansara:modak-api:$version") implementation("io.github.akash-kansara:modak-core:$version") } ``` @@ -35,30 +30,12 @@ dependencies { ### Gradle (Groovy DSL) ```groovy dependencies { - // Add only if you're not using bean validation already - implementation("jakarta.validation:jakarta.validation-api:3.1.0") - - // Modak dependencies - implementation 'io.github.akash-kansara:modak-api:$version' implementation 'io.github.akash-kansara:modak-core:$version' } ``` ### Maven ```xml - - - jakarta.validation - jakarta.validation-api - 3.1.0 - - - - - io.github.akash-kansara - modak-api - VERSION - io.github.akash-kansara modak-core @@ -71,7 +48,7 @@ dependencies { ### 1. Define your corrections ```java -// Annotation: +// Correction annotation: @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Correction(correctedBy = {UserCorrectionApplier.class}) @@ -149,13 +126,12 @@ User user = new User(null, null, null, "example@com!pany.com"); CorrectionResult result = corrector.correct(user); System.out.println(result.isSuccess()); // true +CorrectionResult.Success successResult = (CorrectionResult.Success) result; +System.out.println( // 4 + success.getAppliedCorrections().size() +); +System.out.println(user); // User{name='Anonymous', age=18, role='ADMIN', email='example@company.com'} -if (result instanceof CorrectionResult.Success success) { - System.out.println( // 4 - success.getAppliedCorrections().size() - ); - System.out.println(user); // User{name='Anonymous', age=18, role='ADMIN', email='example@company.com'} -} ``` ## 🔗 Synergy with Bean Validation diff --git a/api/src/main/kotlin/io/github/akashkansara/modak/api/CorrectionResult.kt b/api/src/main/kotlin/io/github/akashkansara/modak/api/CorrectionResult.kt index 9d47ab2..2adeb69 100644 --- a/api/src/main/kotlin/io/github/akashkansara/modak/api/CorrectionResult.kt +++ b/api/src/main/kotlin/io/github/akashkansara/modak/api/CorrectionResult.kt @@ -3,7 +3,7 @@ package io.github.akashkansara.modak.api /** * Result of a correction operation performed by a Corrector. */ -sealed class CorrectionResult { +sealed class CorrectionResult { /** * Indicates whether the correction operation was successful. */ @@ -17,14 +17,14 @@ sealed class CorrectionResult { */ class Success ( val appliedCorrections: List>, - ) : CorrectionResult() + ) : CorrectionResult() /** * Failed correction result. * * @param error The error that occurred during correction */ - class Failure ( - val error: E, - ) : CorrectionResult() + class Failure( + val error: Error, + ) : CorrectionResult() } diff --git a/api/src/main/kotlin/io/github/akashkansara/modak/api/Corrector.kt b/api/src/main/kotlin/io/github/akashkansara/modak/api/Corrector.kt index 3832d55..7ebc7ce 100644 --- a/api/src/main/kotlin/io/github/akashkansara/modak/api/Corrector.kt +++ b/api/src/main/kotlin/io/github/akashkansara/modak/api/Corrector.kt @@ -16,7 +16,7 @@ interface Corrector { fun correct( obj: T, vararg groups: Class<*>, - ): CorrectionResult + ): CorrectionResult /** * Applies corrections to the given object, considering existing constraint violations. @@ -30,5 +30,5 @@ interface Corrector { obj: T, constraintViolations: Set>, vararg groups: Class<*>, - ): CorrectionResult + ): CorrectionResult } diff --git a/api/src/main/kotlin/io/github/akashkansara/modak/api/ErrorLike.kt b/api/src/main/kotlin/io/github/akashkansara/modak/api/Error.kt similarity index 94% rename from api/src/main/kotlin/io/github/akashkansara/modak/api/ErrorLike.kt rename to api/src/main/kotlin/io/github/akashkansara/modak/api/Error.kt index 15e9c16..e6f4ffa 100644 --- a/api/src/main/kotlin/io/github/akashkansara/modak/api/ErrorLike.kt +++ b/api/src/main/kotlin/io/github/akashkansara/modak/api/Error.kt @@ -3,7 +3,7 @@ package io.github.akashkansara.modak.api /** * Represents an error that occurred during correction operations. */ -interface ErrorLike { +interface Error { /** Error message describing what went wrong */ val message: String diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 57e8877..6b411c9 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -18,7 +18,7 @@ dependencies { implementation(libs.arrow.kt.core) implementation(libs.guava) - implementation(libs.jakarta.validation.api) + api(libs.jakarta.validation.api) testImplementation(libs.junit.jupiter) testImplementation(libs.hibernate.validator) diff --git a/core/src/main/kotlin/io/github/akashkansara/modak/core/CorrectorImpl.kt b/core/src/main/kotlin/io/github/akashkansara/modak/core/CorrectorImpl.kt index 8d3e395..6299458 100644 --- a/core/src/main/kotlin/io/github/akashkansara/modak/core/CorrectorImpl.kt +++ b/core/src/main/kotlin/io/github/akashkansara/modak/core/CorrectorImpl.kt @@ -4,9 +4,8 @@ import arrow.core.Either import io.github.akashkansara.modak.api.AppliedCorrection import io.github.akashkansara.modak.api.CorrectionResult import io.github.akashkansara.modak.api.Corrector -import io.github.akashkansara.modak.api.ErrorLike import io.github.akashkansara.modak.core.beanmodification.BeanModifier -import io.github.akashkansara.modak.core.models.ErrorLikeImpl +import io.github.akashkansara.modak.core.models.ErrorImpl import io.github.akashkansara.modak.core.models.InternalError import jakarta.validation.ConstraintViolation @@ -14,7 +13,7 @@ class CorrectorImpl(private val beanModifier: BeanModifier) : Corrector { override fun correct( obj: T, vararg groups: Class<*>, - ): CorrectionResult { + ): CorrectionResult { val groupList = getGroupList(*groups) val modifyBeanResult = beanModifier.modifyBean(obj, null, groupList) return processModificationResult(modifyBeanResult) @@ -24,7 +23,7 @@ class CorrectorImpl(private val beanModifier: BeanModifier) : Corrector { obj: T, constraintViolations: Set>, vararg groups: Class<*>, - ): CorrectionResult { + ): CorrectionResult { val groupList = getGroupList(*groups) val modifyBeanResult = beanModifier.modifyBean(obj, constraintViolations, groupList) return processModificationResult(modifyBeanResult) @@ -40,7 +39,7 @@ class CorrectorImpl(private val beanModifier: BeanModifier) : Corrector { private fun processModificationResult( result: Either>>, - ): CorrectionResult { + ): CorrectionResult { return result.fold( ifLeft = { val appliedCorrections = when (it) { @@ -48,7 +47,7 @@ class CorrectorImpl(private val beanModifier: BeanModifier) : Corrector { else -> emptyList() } CorrectionResult.Failure( - ErrorLikeImpl( + ErrorImpl( message = it.message, cause = it.cause, appliedCorrections = appliedCorrections, diff --git a/core/src/main/kotlin/io/github/akashkansara/modak/core/models/ErrorLikeImpl.kt b/core/src/main/kotlin/io/github/akashkansara/modak/core/models/ErrorImpl.kt similarity index 74% rename from core/src/main/kotlin/io/github/akashkansara/modak/core/models/ErrorLikeImpl.kt rename to core/src/main/kotlin/io/github/akashkansara/modak/core/models/ErrorImpl.kt index 55524a0..3cc09f2 100644 --- a/core/src/main/kotlin/io/github/akashkansara/modak/core/models/ErrorLikeImpl.kt +++ b/core/src/main/kotlin/io/github/akashkansara/modak/core/models/ErrorImpl.kt @@ -1,10 +1,10 @@ package io.github.akashkansara.modak.core.models import io.github.akashkansara.modak.api.AppliedCorrection -import io.github.akashkansara.modak.api.ErrorLike +import io.github.akashkansara.modak.api.Error -data class ErrorLikeImpl( +data class ErrorImpl( override val cause: Throwable? = null, override val message: String, override val appliedCorrections: List> = emptyList(), -) : ErrorLike +) : Error diff --git a/docs/API.md b/docs/API.md index a55b0bf..233b9ac 100644 --- a/docs/API.md +++ b/docs/API.md @@ -55,52 +55,98 @@ This library is inspired by and designed to complement [Jakarta Bean Validation] --- -## 2. Correction definition +## 2. Getting Started -### 2.1. Correction annotation +### 2.1. Setup project -A correction is defined by annotating an annotation with `@Correction` and specifying the correction applier classes that implement the correction logic. +Add the library dependency to your project: +**Maven:** +```xml + + io.github.akash-kansara + modak-core + VERSION +``` +***Gradle (Kotlin DSL):*** ```kotlin -@Target(AnnotationTarget.ANNOTATION_CLASS) -@Retention(AnnotationRetention.RUNTIME) -annotation class Correction( - val correctedBy: Array>> -) +dependencies { + implementation("io.github.akash-kansara:modak-core:$VERSION") +} ``` -#### 2.1.1. Correction definition properties +#### 2.2 Defining corrections -Every correction annotation must include the following properties: +Example: Class User with corrections -```kotlin -val groups: Array> = [] -val payload: Array> = [] -val constraintFilter: Array> = [] -val correctionTarget: CorrectionTarget = CorrectionTarget.PROPERTY +```java +import io.github.akashkansara.modak.api.correction.DefaultValue; +import io.github.akashkansara.modak.api.correction.RegexReplace; +import io.github.akashkansara.modak.api.correction.Trim; + +public class User { + @Trim + @DefaultValue(strValue = "Anonymous") + public String name; + + @DefaultValue(intValue = 18) + public Integer age; + + public String role; + + @RegexReplace( + regexPattern = "[^a-zA-Z0-9@._-]", + replaceStr = "" + ) + public String email; + + public User(String name, Integer age, String role, String email) { + this.name = name; + this.age = age; + this.role = role; + this.email = email; + } +} ``` -- **groups**: Specifies the validation groups for which this correction applies -- **payload**: Allows attaching metadata to the correction -- **constraintFilter**: Specifies which constraint violations trigger this correction -- **correctionTarget**: Defines what should be corrected - property value or container elements. When not supplied, correction is targeted to properties. +_NOTE: If you're using getter/setter methods, you can annotate the getter instead of fields_ -#### 2.1.2. Examples +The `@Trim`, `@DefaultValue` and `@RegexReplace` annotations are used to declare the corrections which should be applied to the fields of a User instance: -```kotlin -@Target(AnnotationTarget.FIELD, AnnotationTarget.TYPE) -@Correction(correctedBy = [StringDefaultValueCorrectionApplier::class]) -annotation class DefaultValue( - val groups: Array> = [], - val payload: Array> = [], - val constraintFilter: Array> = [], - val correctionTarget: CorrectionTarget = CorrectionTarget.PROPERTY, - val strValue: String = "", - val intValue: Int = 0, - // ... other value types -) +- If `name` is null, assign a default value of "Anonymous" +- Trim leading and trailing whitespace from `name` +- If `age` is null, assign a default value of 18 +- Replace any characters from `email` that match the specified regex pattern + +#### 2.3 Applying corrections + +```java +import io.github.akashkansara.modak.api.CorrectionResult; +import io.github.akashkansara.modak.api.Corrector; +import io.github.akashkansara.modak.core.CorrectorFactory; + +public class Main { + public static void main(String[] args) { + Corrector corrector = CorrectorFactory.buildCorrector(); + User user = new User(" John Doe ", null, null, "example@com!pany.com"); + var result = corrector.correct(user); + if (result.isSuccess()) { + CorrectionResult.Success successResult = (CorrectionResult.Success) result; + System.out.println(successResult.getAppliedCorrections().size()); + System.out.println(user); // User{name='John Doe', age=18, role=null, email='example@company.com'} + } else { + CorrectionResult.Failure failure = ((CorrectionResult.Failure) result); + System.out.println(failure.getError().getMessage()); + System.out.println(failure.getError().getCause()); + System.out.println(failure.getError().getAppliedCorrections()); + } + } +} ``` +The `correct()` method returns `CorrectionResult` which has a property `isSuccess` that indicates whether correction was successful. +When `isSuccess` is `true`, you can safely cast the result to which you can iterate over in order to see which validation errors occurred. + ### 2.2. Correction composition Corrections can be composed by applying multiple correction annotations to the same element: From 9f3cbcb8d61cec75cc6836450b4107c0df41b0ab Mon Sep 17 00:00:00 2001 From: Akash Kansara Date: Sat, 13 Sep 2025 15:42:57 +0530 Subject: [PATCH 4/9] Update reference guide --- .../core/group/GroupSequenceGeneratorTest.kt | 5 +- docs/API.md | 906 +++++++----------- 2 files changed, 351 insertions(+), 560 deletions(-) diff --git a/core/src/test/kotlin/io/github/akashkansara/modak/core/group/GroupSequenceGeneratorTest.kt b/core/src/test/kotlin/io/github/akashkansara/modak/core/group/GroupSequenceGeneratorTest.kt index c7816dd..8f20236 100644 --- a/core/src/test/kotlin/io/github/akashkansara/modak/core/group/GroupSequenceGeneratorTest.kt +++ b/core/src/test/kotlin/io/github/akashkansara/modak/core/group/GroupSequenceGeneratorTest.kt @@ -60,8 +60,9 @@ class GroupSequenceGeneratorTest { val result = generator.generateGroupSequence(inputGroups) assertTrue(result.isRight()) val groups = result.getOrNull()!! - assertTrue(groups.contains(RegionalBranchGroup::class.java)) - assertTrue(groups.contains(BranchGroup::class.java)) + assertEquals(2, groups.size) + assertEquals(BranchGroup::class.java, groups[0]) + assertEquals(RegionalBranchGroup::class.java, groups[1]) } @Test diff --git a/docs/API.md b/docs/API.md index 233b9ac..b236cb5 100644 --- a/docs/API.md +++ b/docs/API.md @@ -85,27 +85,27 @@ import io.github.akashkansara.modak.api.correction.RegexReplace; import io.github.akashkansara.modak.api.correction.Trim; public class User { - @Trim - @DefaultValue(strValue = "Anonymous") - public String name; + @Trim + @DefaultValue(strValue = "Anonymous") + public String name; - @DefaultValue(intValue = 18) - public Integer age; + @DefaultValue(intValue = 18) + public Integer age; - public String role; + public String role; - @RegexReplace( - regexPattern = "[^a-zA-Z0-9@._-]", - replaceStr = "" - ) - public String email; - - public User(String name, Integer age, String role, String email) { - this.name = name; - this.age = age; - this.role = role; - this.email = email; - } + @RegexReplace( + regexPattern = "[^a-zA-Z0-9@._-]", + replaceStr = "" + ) + public String email; + + public User(String name, Integer age, String role, String email) { + this.name = name; + this.age = age; + this.role = role; + this.email = email; + } } ``` @@ -145,656 +145,446 @@ public class Main { ``` The `correct()` method returns `CorrectionResult` which has a property `isSuccess` that indicates whether correction was successful. -When `isSuccess` is `true`, you can safely cast the result to which you can iterate over in order to see which validation errors occurred. - -### 2.2. Correction composition - -Corrections can be composed by applying multiple correction annotations to the same element: - -```kotlin -data class User( - @field:Trim - @field:DefaultValue(strValue = "Anonymous") - val name: String? -) -``` -### 2.3. Correction applier implementation +When `isSuccess` is `true`, you can safely cast the result to `CorrectionResult.Success`. This object has `getAppliedCorrections` API returns a list of `AppliedCorrection` which you can use to iterate over applied corrections. -Correction logic is implemented by classes that implement the `CorrectionApplier` interface: +When `isSuccess` is `false`, you can safely cast the result to `CorrectionResult.Failure`. This object has `getError` API which you can use to get details about the failure. This object also has `getAppliedCorrections` API which you can use to iterate over the list of corrections that were applied before the failure occurred. -#### 2.3.1. Type-based correction applier selection +### 3. Declaring and applying corrections -When a correction annotation is applied to a property, the correction engine selects the appropriate `CorrectionApplier` implementation based on **type matching**. The selection process works as follows: +#### 3.1 Declaring corrections -**Example of type-based selection:** +Corrections are declared using annotations. There are 3 types of corrections: +- field / property corrections (Can be applied to fields or getter methods) +- container element corrections (List, Map, Array element corrections) +- class corrections -```kotlin -@Correction(correctedBy = [ - StringDefaultValueCorrectionApplier::class, // CorrectionApplier - IntDefaultValueCorrectionApplier::class, // CorrectionApplier - EnumDefaultValueCorrectionApplier::class // CorrectionApplier> -]) -annotation class DefaultValue(...) - -data class User( - @field:DefaultValue(strValue = "Anonymous") - val name: String?, // → StringDefaultValueCorrectionApplier selected - - @field:DefaultValue(intValue = 18) - val age: Int?, // → IntDefaultValueCorrectionApplier selected - - @field:DefaultValue(enumValueClass = Status::class, enumValueName = "ACTIVE") - val status: Status? // → EnumDefaultValueCorrectionApplier selected +```java +@MyCustomUserCorrection( + defaultRole = "DEFAULT", + adminRole = "ADMIN" ) -``` +public class User { + @Trim + @DefaultValue(strValue = "Anonymous") + public String name; -This type-based selection ensures **type safety** and allows a single correction annotation to support multiple data types through specialized applier implementations. + public String role; -```kotlin -interface CorrectionApplier { - fun initialize(correctionAnnotation: A) { - // Default implementation does nothing - } + @DefaultValue(intValue = 18) + public Integer age; - fun correct(value: T?, context: CorrectionApplierContext?): CorrectionApplierResult + @Trim( + correctionTarget = CorrectionTarget.CONTAINER_ELEMENT + ) + public List contactNumbers; } -``` - -#### 2.3.2. Examples - -```kotlin -class StringDefaultValueCorrectionApplier : CorrectionApplier { - private lateinit var annotation: DefaultValue - override fun initialize(correctionAnnotation: DefaultValue) { - annotation = correctionAnnotation - } - - override fun correct(value: String?, context: CorrectionApplierContext?): CorrectionApplierResult { - return if (value.isNullOrBlank() && annotation.strValue.isNotEmpty()) { - CorrectionApplierResult.Edited( - oldValue = value, - newValue = annotation.strValue - ) - } else { - CorrectionApplierResult.NoChange() - } +class Main { + public static void main(String[] args) { + Corrector corrector = CorrectorFactory.buildCorrector(); + User user = new User(null, null, null, Arrays.asList(" +1-555-123-4567 ", "555-987-6543\n")); + var result = corrector.correct(user); + CorrectionResult.Success successResult = (CorrectionResult.Success) result; + System.out.println(user); + // User{ + // name='Anonymous', + // age=18, + // role='DEFAULT', + // contactNumbers=['+1-555-123-4567', '555-987-6543'] + // } } } ``` ---- - -## 3. Correction declaration and application process - -### 3.1. Requirements on classes to be corrected - -Classes to be corrected must follow standard JavaBean conventions: -- Public default constructor (for object creation during correction) -- Accessible properties (public fields or getter/setter methods) - -### 3.2. Correction declaration - -Corrections are declared using correction annotations on: -- **Fields**: `@field:DefaultValue(strValue = "default")` -- **Properties**: Applied to getter methods -- **Types**: Applied to class declarations -- **Container elements**: Applied to collection/map elements - -### 3.3. Inheritance (interface and superclass) - -Correction declarations are inherited from: -- Superclasses -- Implemented interfaces +- `@MyCustomUserCorrection` is a class-level correction that applies to the entire User object +- `@Trim` and `@DefaultValue` are field-level corrections that apply to the respective fields +- `@Trim(correctionTarget = CorrectionTarget.CONTAINER_ELEMENT)` is a container element correction that applies to each element in the `contactNumbers` list -Subclass declarations override superclass declarations for the same property. +Corrections are inherited: -### 3.4. Group and group sequence - -#### 3.4.1. Group inheritance - -Groups are inherited following the same rules as Jakarta Bean Validation: -- Interface groups are inherited by implementing classes -- Superclass groups are inherited by subclasses - -#### 3.4.2. Group sequence +```java +public class BaseUser { + @Trim + @DefaultValue(strValue = "Anonymous") + public String name; +} -Groups can be ordered using `@GroupSequence`: +public class User extends BaseUser { + @DefaultValue(intValue = 18) + public Integer age; +} -```kotlin -@GroupSequence(BasicGroup::class, AdvancedGroup::class) -interface ValidationSequence +// User.name will have @Trim and @DefaultValue corrections inherited from BaseUser ``` -### 3.5. Nested object corrections - -The `@CorrectNested` annotation enables automatic traversal and correction of nested objects and their properties. - -#### 3.5.1. @CorrectNested annotation +Object graph traversal and nested corrections: -The `@CorrectNested` annotation instructs the correction engine to: -1. **Traverse** into the nested object or each element in collection -2. **Apply corrections** to nested properties based on their annotations -3. **Maintain object relationships** during the correction process - -#### 3.5.2. Nested object correction +```java +public class Headquarters { + @Truncate(length = 100) + public String address; -For single nested objects, `@CorrectNested` enables correction of the nested object's properties: + @DefaultValue(intValue = 2000) + public Integer establishedYear; +} -```kotlin -data class Company( - @field:Trim - val name: String?, +public class Company { + @Trim + public String name; - @field:CorrectNested - val headquarters: Office? // Office properties will be corrected -) + @CorrectNested + public Office headquarters; +} -data class Office( - @field:Trim - @field:DefaultValue(strValue = "Unknown Location") - val address: String?, - - @field:DefaultValue(intValue = 2000) - val establishedYear: Int? -) +// Correction: +Headquarters hq = new Headquarters(" 123 Main St ", null); +Company company = new Company(" Acme Corp ", hq); +CorrectionResult result = corrector.correct(company); +CorrectionResult.Success successResult = (CorrectionResult.Success) result; +System.out.println(company); +// Company{ +// name='Acme Corp', +// headquarters=Office{ +// address='123 Main St', +// establishedYear=2000 +// } +// } ``` -#### 3.5.3. Collection corrections +Here, `@CorrectNested` on the `headquarters` field of `Company` enables automatic traversal into the `Headquarters` object and applies its corrections. -`@CorrectNested` works with all supported container types, applying corrections to each element: +Object graph traversal and nested corrections for container elements: -```kotlin -data class Department( - @field:CorrectNested - val employees: List, // Each Employee will be corrected +```java +public class Employee { + public Boolean isManager; + + @Trim + @DefaultValue(strValue = "Unknown Employee") + public String name; - @field:CorrectNested - val assets: Map // Each Asset will be corrected -) -``` + @DefaultValue(intValue = 18) + public Integer age; +} -#### 3.5.4. Nested correction with groups +public class Branch { + @Trim + public String name; -Group conversion can be applied during nested correction: + @ManagerCorrection( // Targets each Employee in the list + correctionTarget = CorrectionTarget.CONTAINER_ELEMENT + ) + @RemoveDuplicateEmployeesCorrection // Targets the entire list of employees + @CorrectNested // Enables nested correction for each Employee in the list + public List employees; +} -```kotlin -data class Company( - @field:CorrectNested - @field:ConvertGroup(from = Default::class, to = CompanyValidation::class) - val branches: List -) +// Correction: +Employee emp1 = new Employee(null, null, null); +Employee emp2 = new Employee(true, " Alice ", 30); +Employee emp3 = new Employee(true, " Alice ", 30); // Duplicate +Branch branch = new Branch(" Branch 1 ", Arrays.asList(emp1, emp2, emp3)); +CorrectionResult result = corrector.correct(branch); +System.out.println(branch); +// Branch{ +// name='Branch 1', +// employees=[ +// Employee{isManager=false, name='Unknown Employee', age=18}, +// Employee{isManager=true, name='Alice', age=30} +// ] +// } ``` -### 3.6. Container element corrections - -Container element corrections target elements within lists, maps, and arrays rather than the container itself. - -#### 3.6.1. Supported container types - -The library supports three container types: - -| Container Type | Java/Kotlin Types | Description | -|---|---|---| -| **LIST** | `List`, `ArrayList`, `LinkedList`, etc. | Ordered collections with indexed access | -| **MAP** | `Map`, `HashMap`, `LinkedHashMap`, etc. | Key-value pairs with key-based access | -| **ARRAY** | `Array`, `IntArray`, `StringArray`, etc. | Fixed-size arrays including primitive arrays | - -#### 3.6.2. Container type detection - -Container types are detected using the following rules: - -**Examples of supported types:** -- **Lists**: `List`, `ArrayList`, `MutableList` -- **Maps**: `Map`, `HashMap`, `MutableMap` -- **Arrays**: `Array`, `IntArray`, `Array` - -#### 3.6.3. Container element correction syntax - -Use `CorrectionTarget.CONTAINER_ELEMENT` to correct elements within containers: - -```kotlin -data class ContactInfo( - // Correct each phone number string in the list - @field:Trim(correctionTarget = CorrectionTarget.CONTAINER_ELEMENT) - @field:RegexReplace( - regexPattern = "[^0-9+()-]", - replaceStr = "", - correctionTarget = CorrectionTarget.CONTAINER_ELEMENT - ) - val phoneNumbers: List, +Assuming that: +- `@ManagerCorrection` is a custom correction that sets `isManager` to `false` if it's null +- `@RemoveDuplicateEmployeesCorrection` is a custom correction that removes duplicate employees based on `name` and `age` - // Correct each email address in the map values - @field:Trim(correctionTarget = CorrectionTarget.CONTAINER_ELEMENT) - val emailAddresses: Map, +Here, `@CorrectNested` on the `employees` field of `Branch` enables automatic traversal into each `Employee` object in the list and applies their corrections. The `@ManagerCorrection` applies to each `Employee` in the list and `@RemoveDuplicateEmployeesCorrection` applies to the entire list. - // Correct each name in the array - @field:DefaultValue( - strValue = "Unknown", - correctionTarget = CorrectionTarget.CONTAINER_ELEMENT - ) - val names: Array -) -``` +#### 3.2 `AppliedCorrection` -#### 3.6.4. Container element access patterns +The `AppliedCorrection` class provides details about each correction that was applied during the correction process: -The correction engine uses different access patterns for each container type: +- **propertyPath**: The path to the property that was corrected (e.g., "name", "headquarters.address", "employees[0].name") +- **correctionAnnotation**: The annotation instance that triggered the correction (e.g., `@Trim`, `@DefaultValue`) +- **oldValue**: The original value before correction +- **newValue**: The new value after correction +- **correctionApplierClass**: The class of the `CorrectionApplier` that performed the correction (e.g., `TrimCorrectionApplier`, `RemoveDuplicateEmployeesCorrectionApplier`) -#### 3.6.5. Combining container corrections with nested corrections +#### 3.3 Built-in corrections -Container element corrections and nested corrections can be combined: +The library provides several built-in correction annotations such as: -```kotlin -data class Organization( - // Apply @CorrectNested to traverse into each Employee object - // AND apply container element corrections to the list structure - @field:CorrectNested - @field:CustomEmployeeCorrection(correctionTarget = CorrectionTarget.CONTAINER_ELEMENT, groups = [SpecialGroup::class]) - val employees: List -) +**DefaultValue** -data class Employee( - @field:Trim - @field:DefaultValue(strValue = "Unknown") - val name: String? -) -``` +The `@DefaultValue` annotation sets a default value for null fields. It supports multiple data types including String, Integer, Long, Double, Float, Boolean, Character, Byte, Short, and Enum types. -**Correction order:** -1. **Nested object corrections** are applied first via `@CorrectNested` -2. **Container element corrections** are applied second +**Trim** -#### 3.6.6. Complete example: Nested and container corrections +The `@Trim` annotation removes leading and trailing whitespace from string fields. -Here's a comprehensive example demonstrating all correction types: +**Truncate** -```kotlin -data class Company( - @field:Trim - @field:DefaultValue(strValue = "Unnamed Company") - val name: String?, - - // Nested object correction - @field:CorrectNested - val headquarters: Office?, - - // Nested collection with object corrections - @field:CorrectNested - val branches: List, - - // Container element corrections on phone numbers - @field:Trim(correctionTarget = CorrectionTarget.CONTAINER_ELEMENT) - @field:RegexReplace( - regexPattern = "[^0-9+()-]", - replaceStr = "", - correctionTarget = CorrectionTarget.CONTAINER_ELEMENT - ) - val phoneNumbers: MutableList, +The `@Truncate` annotation limits the length of string fields by truncating excess characters from either the start or end, defaulting to truncating from the end. - // Mixed: nested objects in map with container element corrections - @field:CorrectNested - @field:DefaultValue( - strValue = "info@company.com", - correctionTarget = CorrectionTarget.CONTAINER_ELEMENT - ) - val departmentContacts: MutableMap -) +**RegexReplace** -data class Office( - @field:Trim - @field:DefaultValue(strValue = "Unknown Location") - val address: String?, +The `@RegexReplace` annotation replaces text in string fields that match a specified regex pattern with a given replacement string. - @field:DefaultValue(intValue = 2000) - val establishedYear: Int? -) +### 4. Grouping corrections -data class Branch( - @field:Trim - val name: String?, +#### 4.1 Groups - @field:CorrectNested - val employees: List -) +The `correct` method also takes a var-arg argument of groups. Groups allow you to restrict set of corrections that should be applied. -data class Employee( - @field:Trim - @field:DefaultValue(strValue = "Unknown Employee") - val name: String?, +Example: - @field:DefaultValue(intValue = 18) - val age: Int? -) +```java +public interface BasicCorrection { +} -data class Contact( - @field:Trim - @field:DefaultValue(strValue = "Unknown") - val name: String?, +public interface RoleCorrection { +} - @field:Trim(correctionTarget = CorrectionTarget.CONTAINER_ELEMENT) - val emails: MutableList +@MyCustomUserCorrection( + defaultRole = "DEFAULT", + adminRole = "ADMIN", + groups = {RoleCorrection.class} ) +public class User { + @Trim( + groups = {BasicCorrection.class} + ) + @DefaultValue( + strValue = "Anonymous", + groups = {BasicCorrection.class} + ) + public String name; -// Usage example -val company = Company( - name = " ", // Will be corrected to "Unnamed Company" - headquarters = Office( - address = " 123 Main St ", // Will be trimmed - establishedYear = null // Will be set to 2000 - ), - branches = listOf( - Branch( - name = " Branch 1 ", // Will be trimmed - employees = listOf( - Employee(name = null, age = null) // Will get defaults - ) - ) - ), - phoneNumbers = mutableListOf( - " +1-555-123-4567!! ", // Will be trimmed and cleaned - "555.987.6543" // Will be cleaned - ), - departmentContacts = mutableMapOf( - "sales" to Contact( - name = " John Doe ", // Will be trimmed - emails = mutableListOf(" john@company.com ") // Will be trimmed - ) - ) -) + public String role; -val corrector = CorrectorFactory.buildCorrector() -when (val result = corrector.correct(company)) { - is CorrectionResult.Success -> { - val correctedUser = result.correctedObject - val appliedCorrections = result.appliedCorrections - // Result will have all corrections applied: - // - company.name = "Unnamed Company" - // - company.headquarters.address = "123 Main St" - // - company.headquarters.establishedYear = 2000 - // - company.branches[0].name = "Branch 1" - // - company.branches[0].employees[0].name = "Unknown Employee" - // - company.branches[0].employees[0].age = 18 - // - company.phoneNumbers[0] = "+1-555-123-4567" - // - company.phoneNumbers[1] = "5559876543" - // - company.departmentContacts["sales"].name = "John Doe" - // - company.departmentContacts["sales"].emails[0] = "john@company.com" - } - is CorrectionResult.Failure -> { - println("Correction failed: ${result.error.message}") - result.error.appliedCorrections // Corrections applied before the failure happened - } + @DefaultValue( + intValue = 18, + groups = {BasicCorrection.class} + ) + public Integer age; } -``` -### 3.7. Correction routine - -The correction process follows these steps: +User user = new User(null, null, null, null); +CorrectionResult.Success result = (CorrectionResult.Success) corrector.correct(user); +System.out.println(result.getAppliedCorrections().size()); // 0 + +CorrectionResult.Success result = (CorrectionResult.Success) corrector.correct( + user, + BasicCorrection.class +); +System.out.println(result.getAppliedCorrections().size()); // 3 + +CorrectionResult.Success result = (CorrectionResult.Success) corrector.correct( + user, + BasicCorrection.class, RoleCorrection.class // Corrects BasicCorrection first and then RoleCorrection +); +System.out.println(result.getAppliedCorrections().size()); // 4 +``` -1. **Object traversal**: Navigate the object graph using `@CorrectNested` annotations -2. **Correction discovery**: Find applicable corrections based on annotations and groups -3. **Constraint filtering**: Apply corrections only if specified constraints are violated -4. **Correction application**: Execute correction appliers in order -5. **Result collection**: Gather information about applied corrections +The reason why no corrections were applied when groups weren't specified in the 1st `correct` call is because all corrections have been assigned to specific groups. +When corrections are not assigned any group _OR_ they are assigned the `DefaultGroup` group, they are applied when no groups are passed in the `correct` call. ---- +#### 4.2 Group inheritance -## 4. Correction APIs +Groups can also inherit from other groups. This allows you to create a hierarchy of groups and apply corrections based on that hierarchy. -### 4.1. Corrector API +```java +public interface BasicCorrection { +} -Function for applying corrections: +public interface RoleCorrection extends BasicCorrection { +} -```kotlin -fun correct( - obj: T, - vararg groups: Class<*> -): CorrectionResult - -fun correct( - obj: T, - constraintViolations: Set>, - vararg groups: Class<*>, -): CorrectionResult +CorrectionResult.Success result = (CorrectionResult.Success) corrector.correct( + user, + RoleCorrection.class // Corrects BasicCorrection first and then RoleCorrection +); +System.out.println(result.getAppliedCorrections().size()); // 4 ``` -#### 4.1.1. Parameters - -- **obj**: The object to be corrected -- **correctViolationsOnly**: If true, only apply corrections for violated constraints -- **constraintViolations**: Existing constraint violations (optional) -- **groups**: Validation groups to consider for correction - -### 4.2. Bootstrapping - -Create a `Corrector` instance using the factory: - -```kotlin -val corrector = CorrectorFactory.buildCorrector() -corrector.correct(...) -``` +#### 4.3 Group sequences ---- +You might have a requirement to apply corrections in a specific order. For example, you might want to apply basic corrections first and then role-related corrections. +To enforce this, you can use `GroupSequence` annotation. -## 5. Built-in Correction definitions +```java +public interface BasicCorrection { +} -### 5.1. @DefaultValue correction +public interface RoleCorrection { +} -Sets default values for null fields. +@GroupSequence({BasicCorrection.class, RoleCorrection.class}) +public interface OrderedCorrections { +} -```kotlin -@Target(AnnotationTarget.FIELD, AnnotationTarget.TYPE) -@Correction(correctedBy = []) -annotation class DefaultValue( - val groups: Array> = [], - val payload: Array> = [], - val constraintFilter: Array> = [], - val correctionTarget: CorrectionTarget = CorrectionTarget.PROPERTY, - val strValue: String = "", - val intValue: Int = 0, - val longValue: Long = 0L, - val doubleValue: Double = 0.0, - val floatValue: Float = 0.0f, - val booleanValue: Boolean = false, - val charValue: Char = '\u0000', - val byteValue: Byte = 0, - val shortValue: Short = 0, - val enumValueClass: KClass> = Nothing::class, - val enumValueName: String = "" -) +CorrectionResult.Success result = (CorrectionResult.Success) corrector.correct( + user, + OrderedCorrections.class // Corrects BasicCorrection first and then RoleCorrection +); +System.out.println(result.getAppliedCorrections().size()); // 4 ``` -**Supported types**: String, Integer, Long, Double, Float, Boolean, Character, Byte, Short, Enum types - -**Example**: -```kotlin -data class User( - @field:DefaultValue(strValue = "Anonymous") - val name: String?, +### 5. Creating custom corrections - @field:DefaultValue(intValue = 18) - val age: Int?, - - @field:DefaultValue(enumValueClass = Status::class, enumValueName = "ACTIVE") - val status: Status? -) -``` +To create a custom correction, 2 things are needed: +- A correction annotation +- Implement a correction applier -### 5.2. @Trim correction +Let's consider the following model: -Removes leading and trailing whitespace from strings. +```java +public class Phone { + public String countryCode; + public String number; -```kotlin -@Target(AnnotationTarget.FIELD, AnnotationTarget.TYPE) -@Correction(correctedBy = []) -annotation class Trim( - val groups: Array> = [], - val payload: Array> = [], - val constraintFilter: Array> = [], - val correctionTarget: CorrectionTarget = CorrectionTarget.PROPERTY -) + public Phone(String countryCode, String number) { + this.countryCode = countryCode; + this.number = number; + } +} ``` -**Supported types**: String +Now, if we wanted to create a custom correction that adds a default value to country code if it's null, we would do the following: -**Example**: -```kotlin -data class Contact( - @field:Trim - val name: String?, - - @field:Trim - val address: String? -) +```java +import io.github.akashkansara.modak.api.Correction; +import io.github.akashkansara.modak.api.CorrectionTarget; + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Correction(correctedBy = {PhoneCorrectionApplier.class}) +@Retention(RetentionPolicy.RUNTIME) +public @interface PhoneCorrection { + String defaultCountryCode() default "+1"; + Class[] groups() default {}; + Class[] payload() default {}; + CorrectionTarget correctionTarget() default CorrectionTarget.PROPERTY; +} ``` -### 5.3. @Truncate correction - -Limits string length by truncating excess characters. - -```kotlin -@Target(AnnotationTarget.FIELD, AnnotationTarget.TYPE) -@Correction(correctedBy = []) -annotation class Truncate( - val groups: Array> = [], - val payload: Array> = [], - val constraintFilter: Array> = [], - val correctionTarget: CorrectionTarget = CorrectionTarget.PROPERTY, - val length: Int, - val fromEnd: Boolean = true -) -``` +Implementation of the correction applier: -**Parameters**: -- **length**: Maximum allowed length (must be positive) -- **fromEnd**: If `true`, truncates from end; if `false`, truncates from start. Defaults to `true` +```java +import io.github.akashkansara.modak.api.CorrectionApplier; +import io.github.akashkansara.modak.api.CorrectionApplierContext; +import io.github.akashkansara.modak.api.CorrectionApplierResult; -**Supported types**: String +public class PhoneCorrectionApplier implements CorrectionApplier { + private String defaultCountryCode; -**Example**: -```kotlin -data class Post( - @field:Truncate(length = 50) - val title: String?, + @Override + public void initialize(PhoneCorrection annotation) { + this.defaultCountryCode = annotation.defaultCountryCode(); + } - @field:Truncate(length = 200, fromEnd = false) - val summary: String? -) + @Override + public PhoneCorrectionApplier correct(Phone phone, CorrectionApplierContext context) { + if (phone.countryCode == null) { + Phone newPhone = new Phone(this.defaultCountryCode, phone.number); + return new CorrectionApplierResult.Edited<>(phone, newPhone); + } else { + return new CorrectionApplierResult.NoChange<>(); + } + } +} ``` -### 5.4. @RegexReplace correction - -Replaces text matching a regex pattern. +Simple usage: -```kotlin -@Target(AnnotationTarget.FIELD, AnnotationTarget.TYPE) -@Correction(correctedBy = []) -annotation class RegexReplace( - val groups: Array> = [], - val payload: Array> = [], - val constraintFilter: Array> = [], - val correctionTarget: CorrectionTarget = CorrectionTarget.PROPERTY, - val regexPattern: String, - val replaceStr: String -) -``` - -**Parameters**: -- **regexPattern**: The regex pattern to match -- **replaceStr**: The replacement string +```java +public class User { + public String name; + @PhoneCorrection() + public Phone phone; -**Supported types**: String + public User(String name, Phone phone) { + this.name = name; + this.phone = phone; + } +} -**Example**: -```kotlin -data class PhoneNumber( - @field:RegexReplace( - regexPattern = "[^0-9+()-]", - replaceStr = "" - ) - val number: String? -) +User user = new User("John Doe", new Phone(null, "555-123-4567")); +CorrectionResult.Success result = (CorrectionResult.Success) corrector.correct(user); +System.out.println(user); // User{name='John Doe', phone=Phone{countryCode='+1', number='555-123-4567'}} +System.out.println( // Phone{countryCode='null', number='555-123-4567'} + successResult.getAppliedCorrections().get(0).getOldValue() +); +System.out.println( // Phone{countryCode='+1', number='555-123-4567'} + successResult.getAppliedCorrections().get(0).getNewValue() +); ``` ---- - -## 6. Integration +Container element correction: -### 6.1. Jakarta Bean Validation integration - -The library integrates seamlessly with Jakarta Bean Validation: - -```kotlin -data class User( - @field:Size(min = 3, max = 50) - @field:Truncate( - length = 50, - constraintFilter = [Size::class] // Truncate will apply only if there was a Size constraint violation +```java +public class User { + public String name; + @PhoneCorrection( + defaultCountryCode = "+45", + correctionTarget = CorrectionTarget.CONTAINER_ELEMENT ) - @field:DefaultValue(strValue = "Anonymous") - val name: String? -) + public List phones; -// Corrections can be applied before or after validation -val corrector = CorrectorFactory.buildCorrector() -val validator = Validation.buildDefaultValidatorFactory().validator + public User(String name, List phones) { + this.name = name; + this.phones = phones; + } +} -val violations = validator.validate(correctionResult.correctedObject) -val correctionResult = corrector.correct( - user, - violations -) +User user = new User("John Doe", Arrays.asList( + new Phone(null, "555-123-4567"), + new Phone("+44", "020 7946 0958") +)); +CorrectionResult.Success result = (CorrectionResult.Success) corrector.correct(user); +System.out.println(user); // User{name='John Doe', phones=[Phone{countryCode='+45', number='555-123-4567'}, Phone{countryCode='+44', number='020 7946 0958'}]} ``` -### 6.2. Constraint filtering - -Corrections can be configured to only apply when specific constraints are violated: +Corrections can be applied to classes as well: -```kotlin -data class User( - @field:NotNull - @field:Size(min = 3) - @field:DefaultValue( - strValue = "DefaultUser", - constraintFilter = [NotNull::class] // Only apply when @NotNull fails - ) - val username: String? -) -``` - -**Constraint filtering behavior**: -- If `constraintFilter` is empty, the correction always applies -- If `constraintFilter` is specified, the correction only applies if one of the specified constraints is violated -- Multiple constraint types can be specified in the filter - -**Example with multiple constraints**: -```kotlin -@field:DefaultValue( - intValue = 18, - constraintFilter = [NotNull::class, Min::class] +```java +@UserCorrection( + defaultRole = "DEFAULT", + adminRole = "ADMIN" ) -val age: Int? // Applies if @NotNull or @Min constraint fails +public class User { + ... +} ``` ---- +### 6. Integrating with Jakarta Bean Validation -## Appendix A: CorrectionTarget enumeration +The library can be integrated with Jakarta Bean Validation seamlessly. The integration feature allows you to pass in constraint violations to the `correct` method, and it will only apply corrections that are relevant to those violations. +You can additionally supply groups as well to further control sequence of corrections as discussed above. -Note on Correction Target: +```java +public class User { + @NotNull + @DefaultValue( + strValue = "Anonymous", + constraintFilter = {NotNull.class} // Only apply if NotNull constraint fails + ) + public String name; -```kotlin -data class ContactNumber( - @field:DefaultValue(strValue = "123456789") - val value: String? -) + @Min(18) + @MinAgeCorrection( + value = 18, + constraintFilter = {Min.class} // Only apply if Min constraint fails + ) + public Integer age; +} -data class User( - @field:CorrectNested // Ensures correction defined on `ContactNumber` are applied such as ContactNumber.value - @field:MyCustomContactNumberCorrection(correctionTarget = CorrectionTarget.PROPERTY) // Applies to each element in `contactNumbers` list - @field:MyCustomListCorrection // Applies to `contactNumbers` - val contactNumbers: List -) +User user = new User(null, 15); +Set> violations = validator.validate(user); +CorrectionResult.Success result = (CorrectionResult.Success) corrector.correct( + user, + violations // Only apply corrections relevant to these violations +); +System.out.println(user); // User{name='Anonymous', age=18} ``` - ---- From 97854d627a400ece5ef689cd4ca32950e4e3e30c Mon Sep 17 00:00:00 2001 From: Akash Kansara Date: Sat, 13 Sep 2025 15:44:40 +0530 Subject: [PATCH 5/9] Update reference guide --- README.md | 4 ++-- docs/{API.md => REFERENCE_GUIDE.md} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename docs/{API.md => REFERENCE_GUIDE.md} (100%) diff --git a/README.md b/README.md index bbe7928..2b928d4 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ System.out.println(user.getName()); // Anonymous ## 📚 Documentation -**📖 [Full Documentation](docs/API.md)** +**📖 [Full Documentation](docs/REFERENCE_GUIDE.md)** ## ✨ Key Features @@ -208,4 +208,4 @@ Licensed under the terms in the [LICENSE](LICENSE) file. --- -**Need help?** [Open an issue](https://github.com/akash-kansara/modak/issues) or check the [full documentation](docs/API.md). +**Need help?** [Open an issue](https://github.com/akash-kansara/modak/issues) or check the [full documentation](docs/REFERENCE_GUIDE.md). diff --git a/docs/API.md b/docs/REFERENCE_GUIDE.md similarity index 100% rename from docs/API.md rename to docs/REFERENCE_GUIDE.md From f64fcb993b26c75c7ab1fc63be087018de7b7e66 Mon Sep 17 00:00:00 2001 From: Akash Kansara Date: Sat, 13 Sep 2025 15:45:41 +0530 Subject: [PATCH 6/9] Update reference guide table of contents --- docs/REFERENCE_GUIDE.md | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/docs/REFERENCE_GUIDE.md b/docs/REFERENCE_GUIDE.md index b236cb5..128dd42 100644 --- a/docs/REFERENCE_GUIDE.md +++ b/docs/REFERENCE_GUIDE.md @@ -3,30 +3,20 @@ ## Table of Contents * [1. Introduction](#1-introduction) -* [2. Correction definition](#2-correction-definition) - * [2.1. Correction annotation](#21-correction-annotation) - * [2.2. Correction composition](#22-correction-composition) - * [2.3. Correction applier implementation](#23-correction-applier-implementation) -* [3. Correction declaration and application process](#3-correction-declaration-and-application-process) - * [3.1. Requirements on classes to be corrected](#31-requirements-on-classes-to-be-corrected) - * [3.2. Correction declaration](#32-correction-declaration) - * [3.3. Inheritance (interface and superclass)](#33-inheritance-interface-and-superclass) - * [3.4. Group and group sequence](#34-group-and-group-sequence) - * [3.5. Nested object corrections](#35-nested-object-corrections) - * [3.6. Container element corrections](#36-container-element-corrections) - * [3.7. Correction routine](#37-correction-routine) -* [4. Correction APIs](#4-correction-apis) - * [4.1. Corrector API](#41-corrector-api) - * [4.2. Bootstrapping](#42-bootstrapping) -* [5. Built-in Correction definitions](#5-built-in-correction-definitions) - * [5.1. @DefaultValue correction](#51-defaultvalue-correction) - * [5.2. @Trim correction](#52-trim-correction) - * [5.3. @Truncate correction](#53-truncate-correction) - * [5.4. @RegexReplace correction](#54-regexreplace-correction) -* [6. Integration](#6-integration) - * [6.1. Jakarta Bean Validation integration](#61-jakarta-bean-validation-integration) - * [6.2. Constraint filtering](#62-constraint-filtering) -* [Appendix A: CorrectionTarget enumeration](#appendix-a-correctiontarget-enumeration) +* [2. Getting Started](#2-getting-started) + * [2.1. Setup project](#21-setup-project) + * [2.2 Defining corrections](#22-defining-corrections) + * [2.3 Applying corrections](#23-applying-corrections) +* [3. Declaring and applying corrections](#3-declaring-and-applying-corrections) + * [3.1 Declaring corrections](#31-declaring-corrections) + * [3.2 AppliedCorrection](#32-appliedcorrection) + * [3.3 Built-in corrections](#33-built-in-corrections) +* [4. Grouping corrections](#4-grouping-corrections) + * [4.1 Groups](#41-groups) + * [4.2 Group inheritance](#42-group-inheritance) + * [4.3 Group sequences](#43-group-sequences) +* [5. Creating custom corrections](#5-creating-custom-corrections) +* [6. Integrating with Jakarta Bean Validation](#6-integrating-with-jakarta-bean-validation) --- From 33f7b539429a4c8dc853758d03055f34b7744bab Mon Sep 17 00:00:00 2001 From: Akash Kansara Date: Sat, 13 Sep 2025 15:55:19 +0530 Subject: [PATCH 7/9] Update reference guide table of contents --- docs/REFERENCE_GUIDE.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/REFERENCE_GUIDE.md b/docs/REFERENCE_GUIDE.md index 128dd42..14bdbe6 100644 --- a/docs/REFERENCE_GUIDE.md +++ b/docs/REFERENCE_GUIDE.md @@ -22,7 +22,7 @@ ## 1. Introduction -The library provides a framework for automatically correcting data validation issues using annotation-based corrections. +The library provides a framework for automatically correcting inconsistent or invalid data using annotation-based correction rules. It does not perform validation itself, but can complement Bean Validation to correct data when constraints fail. The primary goals of this specification are: @@ -56,6 +56,7 @@ Add the library dependency to your project: io.github.akash-kansara modak-core VERSION + ``` ***Gradle (Kotlin DSL):*** @@ -224,7 +225,7 @@ public class Company { public String name; @CorrectNested - public Office headquarters; + public Headquarters headquarters; } // Correction: @@ -378,8 +379,7 @@ CorrectionResult.Success result = (CorrectionResult.Success) correct System.out.println(result.getAppliedCorrections().size()); // 4 ``` -The reason why no corrections were applied when groups weren't specified in the 1st `correct` call is because all corrections have been assigned to specific groups. -When corrections are not assigned any group _OR_ they are assigned the `DefaultGroup` group, they are applied when no groups are passed in the `correct` call. +If all corrections are explicitly assigned to groups, calling `correct()` without specifying groups applies none of them. To get corrections by default, either omit the `groups` parameter in annotations, or use `DefaultGroup`. #### 4.2 Group inheritance @@ -405,6 +405,8 @@ You might have a requirement to apply corrections in a specific order. For examp To enforce this, you can use `GroupSequence` annotation. ```java +import io.github.akashkansara.modak.api.GroupSequence; + public interface BasicCorrection { } @@ -475,7 +477,7 @@ public class PhoneCorrectionApplier implements CorrectionApplier correct(Phone phone, CorrectionApplierContext context) { + public CorrectionApplierResult correct(Phone phone, CorrectionApplierContext context) { if (phone.countryCode == null) { Phone newPhone = new Phone(this.defaultCountryCode, phone.number); return new CorrectionApplierResult.Edited<>(phone, newPhone); @@ -503,7 +505,7 @@ public class User { User user = new User("John Doe", new Phone(null, "555-123-4567")); CorrectionResult.Success result = (CorrectionResult.Success) corrector.correct(user); System.out.println(user); // User{name='John Doe', phone=Phone{countryCode='+1', number='555-123-4567'}} -System.out.println( // Phone{countryCode='null', number='555-123-4567'} +System.out.println( // Phone{countryCode=null, number='555-123-4567'} successResult.getAppliedCorrections().get(0).getOldValue() ); System.out.println( // Phone{countryCode='+1', number='555-123-4567'} @@ -550,7 +552,8 @@ public class User { ### 6. Integrating with Jakarta Bean Validation -The library can be integrated with Jakarta Bean Validation seamlessly. The integration feature allows you to pass in constraint violations to the `correct` method, and it will only apply corrections that are relevant to those violations. +The library can optionally integrate with Jakarta Bean Validation. When integrated, you can pass constraint violations to correct(), and Modak will apply only corrections relevant to those violations. + You can additionally supply groups as well to further control sequence of corrections as discussed above. ```java From 79db36ccec4276cd0e6bc9959d7c16104eaafee4 Mon Sep 17 00:00:00 2001 From: Akash Kansara Date: Sat, 13 Sep 2025 15:59:10 +0530 Subject: [PATCH 8/9] Update readme --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2b928d4..c07ef48 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![License](https://img.shields.io/github/license/akash-kansara/modak)](LICENSE) [![Maven Central](https://img.shields.io/maven-central/v/io.github.akash-kansara/modak-core)](https://search.maven.org/search?q=g:io.github.akash-kansara) -Modak is a library to that helps you define data correction rules and provides APIs to correct data in your objects based on those rules. Main features: +Modak is a library that helps you define data correction rules and provides APIs to correct data in your objects based on those rules. Main features: 1. Lets you express data correction rules on object models via annotations 2. Lets you write custom constraint in an extensible way @@ -20,6 +20,8 @@ Modak is a library to that helps you define data correction rules and provides A ## 📦 Installation +Available on [Maven Central](https://central.sonatype.com/artifact/io.github.akash-kansara/modak-core?smo=true) + ### Gradle (Kotlin DSL) ```kotlin dependencies { @@ -128,7 +130,7 @@ CorrectionResult result = corrector.correct(user); System.out.println(result.isSuccess()); // true CorrectionResult.Success successResult = (CorrectionResult.Success) result; System.out.println( // 4 - success.getAppliedCorrections().size() + successResult.getAppliedCorrections().size() ); System.out.println(user); // User{name='Anonymous', age=18, role='ADMIN', email='example@company.com'} @@ -180,9 +182,9 @@ System.out.println(user.getName()); // Anonymous ## ✨ Key Features -🔧 **Automatic Data Correction** - Fix data issues +🔧 **Automatic Data Correction** - Automatically fix inconsistent or invalid data -📝 **Annotation-Based** - Simple annotations to define correction rules +📝 **Annotation-Based** - Use simple, declarative annotations to define correction rules 🔗 **Jakarta Validation Integration** - Works with existing validation constraints From b99ce865345fe890599f308f7e358ffebd88bc6b Mon Sep 17 00:00:00 2001 From: Akash Kansara Date: Sat, 13 Sep 2025 16:00:53 +0530 Subject: [PATCH 9/9] Update readme --- SECURITY.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index 0728d54..a70d6fe 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,8 @@ | Version | Supported | |---------|--------------------| -| 1.1.x | :white_check_mark: | +| 1.2.x | :white_check_mark: | +| 1.1.x | :warning: | | 1.0.x | :warning: | ## Reporting a Vulnerability