Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/scala.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
java-version: '21'
cache: 'sbt'

- name: Set up sbt
uses: sbt/setup-sbt@v1

- name: Check formatting and code style
- name: Run all checks (style, formatting, API compatibility)
run: sbt check

- name: Run all tests on JVM
Expand All @@ -51,11 +51,11 @@ jobs:
with:
fetch-depth: 0 # Fetch full history for dynver/release notes

- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
java-version: '21'
cache: 'sbt'

- name: Set up sbt launcher
Expand Down
139 changes: 122 additions & 17 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,116 @@
# Migration Guide

## Migrating from v0.4.8 to v0.5.0

Version 0.5.0 introduces several new features while maintaining backward compatibility with v0.4.8:

1. **New ValidationObserver trait** for observing validation outcomes without altering the flow
2. **New valar-translator module** for internationalization support of validation error messages
3. **Enhanced ValarSuite** with improved testing utilities
4. **Reworked macros** for better performance and modern Scala 3 features
5. **MiMa checks** to ensure binary compatibility between versions

### Update build.sbt:

```scala
// Update core library
libraryDependencies += "net.ghoula" %%% "valar-core" % "0.5.0"

// Add the optional translator module (if needed)
libraryDependencies += "net.ghoula" %%% "valar-translator" % "0.5.0"

// Update testing utilities (if used)
libraryDependencies += "net.ghoula" %%% "valar-munit" % "0.5.0" % Test
```

Your existing validation code will continue to work without any changes.

### Using the New Features

#### Core Extensibility Pattern (ValidationObserver)

The ValidationObserver pattern has been added to valar-core as the **standard way to extend Valar**. This pattern provides:

* A consistent API for integrating with external systems
* Zero-cost abstractions when extensions aren't used
* Type-safe composition with other Valar features

Future Valar modules (like valar-cats-effect and valar-zio) will build upon this pattern, making it the **recommended approach** for anyone building custom Valar extensions.

The ValidationObserver trait allows you to observe validation results without altering the flow:

```scala
import net.ghoula.valar.*
import org.slf4j.LoggerFactory

// Define a custom observer that logs validation results
given loggingObserver: ValidationObserver with {
private val logger = LoggerFactory.getLogger("ValidationAnalytics")

def onResult[A](result: ValidationResult[A]): Unit = result match {
case ValidationResult.Valid(_) =>
logger.info("Validation succeeded")
case ValidationResult.Invalid(errors) =>
logger.warn(s"Validation failed with ${errors.size} errors: ${errors.map(_.message).mkString(", ")}")
}
}

// Use the observer in your validation flow
val result = User.validate(user)
.observe() // The observer's onResult is called here
.map(_.toUpperCase)
```

Key features of ValidationObserver:

* **Zero Overhead**: When using the default no-op observer, the compiler eliminates all observer-related code
* **Non-Intrusive**: Observes validation results without altering the validation flow
* **Chainable**: Works seamlessly with other operations in the validation pipeline
* **Flexible**: Can be used for logging, metrics, alerting, or any other side effect

#### valar-translator

The valar-translator module provides internationalization support:

```scala
import net.ghoula.valar.*
import net.ghoula.valar.translator.Translator

// --- Example Setup ---
// In a real application, this would come from a properties file or other i18n system.
val translations: Map[String, String] = Map(
"error.string.nonEmpty" -> "The field must not be empty.",
"error.int.nonNegative" -> "The value cannot be negative.",
"error.unknown" -> "An unexpected validation error occurred."
)

// --- Implementation of the Translator trait ---
given myTranslator: Translator with {
def translate(error: ValidationError): String = {
// Logic to look up the error's key in your translation map.
// The `.getOrElse` provides a safe fallback.
translations.getOrElse(
error.key.getOrElse("error.unknown"),
error.message // Fall back to the original message if the key is not found
)
}
}

// Use the translator in your validation flow
val result = User.validate(user)
.observe() // Optional: observe the raw result first
.translateErrors() // Translate errors for user presentation
```

The `valar-translator` module is designed to:

* Integrate with any i18n library through the `Translator` typeclass
* Compose cleanly with other Valar features like ValidationObserver
* Provide a clear separation between validation logic and presentation concerns

## Migrating from v0.3.0 to v0.4.8

The main breaking change since v0.4.0 is the artifact name has changed from valar to valar-core to support the new modular
architecture.
The main breaking change in v0.4.0 was the **artifact name change** from valar to valar-core to support the new modular architecture.

### Update build.sbt:

Expand All @@ -12,26 +119,24 @@ architecture.
libraryDependencies += "net.ghoula" %% "valar" % "0.3.0"

// With this (note the triple %%% for cross-platform support):
libraryDependencies += "net.ghoula" %%% "valar-core" % "0.4.8"
libraryDependencies += "net.ghoula" %%% "valar-core" % "0.4.8-bundle"

// Add optional testing utilities (if desired):
libraryDependencies += "net.ghoula" %%% "valar-munit" % "0.4.8" % Test

// Alternatively, use bundle versions with all dependencies included:
libraryDependencies += "net.ghoula" %%% "valar-core" % "0.4.8-bundle"
libraryDependencies += "net.ghoula" %%% "valar-munit" % "0.4.8-bundle" % Test
```

### Available Artifacts
> **Note:** v0.4.8 used bundle versions (`-bundle` suffix) that included all dependencies. Starting from v0.5.0, we've moved to the standard approach without bundle versions for simpler dependency management.

### Available Artifacts for v0.4.8

The `%%%` operator in sbt will automatically select the appropriate artifact for your platform (JVM or Native). If you need to reference a specific artifact directly, here are all the available options:
The `%%%` operator in sbt will automatically select the appropriate artifact for your platform (JVM or Native). For v0.4.8, only bundle versions are available:

| Module | Platform | Artifact ID | Standard Version | Bundle Version |
|--------|----------|-------------------------|------------------------------------------------------|-------------------------------------------------------------|
| Core | JVM | valar-core_3 | `"net.ghoula" %% "valar-core" % "0.4.8"` | `"net.ghoula" %% "valar-core" % "0.4.8-bundle"` |
| Core | Native | valar-core_native0.5_3 | `"net.ghoula" % "valar-core_native0.5_3" % "0.4.8"` | `"net.ghoula" % "valar-core_native0.5_3" % "0.4.8-bundle"` |
| MUnit | JVM | valar-munit_3 | `"net.ghoula" %% "valar-munit" % "0.4.8"` | `"net.ghoula" %% "valar-munit" % "0.4.8-bundle"` |
| MUnit | Native | valar-munit_native0.5_3 | `"net.ghoula" % "valar-munit_native0.5_3" % "0.4.8"` | `"net.ghoula" % "valar-munit_native0.5_3" % "0.4.8-bundle"` |
| Module | Platform | Artifact ID | Bundle Version |
|--------|----------|-------------------------|-------------------------------------------------------------|
| Core | JVM | valar-core_3 | `"net.ghoula" %% "valar-core" % "0.4.8-bundle"` |
| Core | Native | valar-core_native0.5_3 | `"net.ghoula" % "valar-core_native0.5_3" % "0.4.8-bundle"` |
| MUnit | JVM | valar-munit_3 | `"net.ghoula" %% "valar-munit" % "0.4.8-bundle"` |
| MUnit | Native | valar-munit_native0.5_3 | `"net.ghoula" % "valar-munit_native0.5_3" % "0.4.8-bundle"` |

Your existing validation code will continue to work without any changes.

Expand Down Expand Up @@ -71,7 +176,7 @@ val result = summon[Validator[Email]].validate(email)
given stringValidator: Validator[String] with { ... }
given emailValidator: Validator[Email] with { ... }
}

// Be explicit about which one to use
import validators.emailValidator
```
Expand All @@ -81,7 +186,7 @@ val result = summon[Validator[Email]].validate(email)
```scala
given generalStringValidator: Validator[String] with { ... }
given specificEmailValidator: Validator[Email] with { ... }

// Use the specific one explicitly
val result = specificEmailValidator.validate(email)
```
Expand Down
Loading
Loading