Chapitre 02.04 — Invariants#
Sous-DSL d’Ocarina dédié à l’expression d’invariants typés et composables. Utilisé partout : par la CLI, par les POMs, par les
custom_invariants/testing/qui valident la cohérence des suites avant exécution.
Plan#
| # | Fichier | Sujet |
|---|---|---|
| 01 | 01-validate-flow.md | Le flot complet validate(value) → assert_that → execute → raise_if_invalid + la classe _ValidationChain. |
| 02 | 02-assertions.md | Catalogue des assertions builtin (is_str, is_email, is_positive, is_in, has_unique_elements, each, is_valid_filename, …). |
| 03 | 03-otherwise-any-of.md | .otherwise(...) et la combinaison OR via _any_of. |
| 04 | 04-then-chain-of-validations.md | .then(new_value) et chain_validations(...) pour la composition. |
| 05 | 05-business-vs-framework-validator.md | BusinessInvariantValidator vs FrameworkInvariantValidator |
| 06 | 06-invariant-errors.md | InvariantViolationError, DuplicatesError, AggregateInvariantViolationError. |
Le pourquoi du DSL d’invariants#
- Garde-fou avant exécution.
TestSuite.__init__valide avant de lancer quoi que ce soit que les noms / IDs des tests sont uniques, quemax_workers >= 1, etc. - Validation CLI. Chaque flag a son
validate=lambda chain: chain.assert_that(...)dans leCliStore. Les erreurs s’agrègent et sortent en un seul message d’erreur. - Validation côté POM. Les actions de POM peuvent valider leurs paramètres («
retriesdoit être positif ») viavalidate(retries, name="retries").assert_that(is_positive).execute().raise_if_invalid().
Le Holy Book le résume :
Utilisable dans les POMs,
validatepermet d’exprimer des invariants sous forme de chaînes. L’exécution est différée : il faut appeler.execute()explicitement.Le résultat expose
is_valid,errorsetvalidated_values. Il est inerte par défaut..raise_if_invalid()remonte l’exception si besoin.
Principes de conception#
| Principe | Réalisation |
|---|---|
| Exécution différée | .execute() est obligatoire. Avant ça, la chaîne est composable mais inerte. |
| Erreurs agrégées | _ValidationChain exécute tous les steps et collecte les erreurs ; pas de fail-fast. |
| Composition typée | .then(U) change de type ; le T est conservé dans la chaîne. |
| OR logique | .otherwise(pred) combine avec le précédent via _any_of. |
| Type safety | validate("lol").assert_that(is_positive) provoque une erreur mypy (prédicat incompatible). |
| Réutilisabilité | BusinessInvariantValidator.create(...) ou FrameworkInvariantValidator.create(...) pour factoriser un invariant. |
Lien fort avec ROP#
Le sous-DSL invariants suit la même grammaire que ROP :
| ROP | Invariants |
|---|---|
ActionStart[T] → ActionFailure[T] → ActionSuccess[T] → ActionChain[T] | ValidationStartBlock[T] → ValidationAssertBlock[T] → _ValidationResult |
Composition flat via chain_actions(*) | Composition flat via chain_validations(*) |
Évaluation paresseuse (Thunk[ActionChain[T]]) | Évaluation paresseuse (.execute()) |
Result[T] | _ValidationResult (avec is_valid, errors, validated_values) |
Erreur agrégée par Fail | Erreur agrégée par AggregateInvariantViolationError |
C’est le même pattern appliqué à un autre domaine. Voir ../03-railway/ pour le pattern d’origine.