<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Invariants on Ocarina — précis</title>
    <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/series/invariants/</link>
    <description>Recent content in Invariants on Ocarina — précis</description>
    <generator>Hugo</generator>
    <language>fr-FR</language>
    <lastBuildDate>Thu, 04 Jun 2026 21:25:43 +0200</lastBuildDate>
    <atom:link href="https://mojo-molotov.github.io/from-ocarina-to-igor/fr/series/invariants/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>02.04.01 — Le flot validate → assert_that → execute → raise_if_invalid</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/01-validate-flow/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/01-validate-flow/</guid>
      <description>&lt;h1 id=&#34;020401le-flot-validate--assert_that--execute--raise_if_invalid&#34;&gt;02.04.01 — Le flot &lt;code&gt;validate → assert_that → execute → raise_if_invalid&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020401le-flot-validate--assert_that--execute--raise_if_invalid&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/validate.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/invariants/validate.py&lt;/code&gt;&lt;/a&gt; (entry-point) + &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/internals/validation_chain.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/invariants/internals/validation_chain.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;la-machine-à-états&#34;&gt;La machine à états&lt;a class=&#34;anchor&#34; href=&#34;#la-machine-%c3%a0-%c3%a9tats&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;diagramme&#34;&gt;Diagramme&lt;a class=&#34;anchor&#34; href=&#34;#diagramme&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;validate(value: T)&#xA;       │&#xA;       ▼&#xA;ValidationStartBlock[T]&#xA;       │&#xA;       └─ assert_that(predicate, *, msg=None)&#xA;              │&#xA;              ▼&#xA;       ValidationAssertBlock[T]   ← état qui accepte plusieurs continuations&#xA;              │&#xA;              ├─► assert_that(P)     [boucle, AND chaînable, voir le diagramme dédié]&#xA;              ├─► otherwise(P)       [OR, voir 03-otherwise-any-of.md]&#xA;              ├─► then(U, name=...)  [change de valeur, voir 04-then-chain-of-validations.md]&#xA;              └─► execute()          → _ValidationResult&#xA;                                          │&#xA;                                          ├─ is_valid: bool&#xA;                                          ├─ errors: Sequence[InvariantViolationError]&#xA;                                          ├─ validated_values: Sequence[Any]&#xA;                                          └─ raise_if_invalid() : None | raise AggregateInvariantViolationError&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;boucle-assert_that&#34;&gt;Boucle &lt;code&gt;assert_that&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#boucle-assert_that&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;assert_that&lt;/code&gt; formule &lt;strong&gt;une&lt;/strong&gt; assertion sur la valeur courante. Plusieurs &lt;code&gt;assert_that&lt;/code&gt; peuvent se suivre, chacun ajoutant une condition supplémentaire à la chaîne (AND logique, ordre préservé).&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.04.02 — Catalogue des assertions builtin</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/02-assertions/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/02-assertions/</guid>
      <description>&lt;h1 id=&#34;020402catalogue-des-assertions-builtin&#34;&gt;02.04.02 — Catalogue des assertions builtin&lt;a class=&#34;anchor&#34; href=&#34;#020402catalogue-des-assertions-builtin&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/assertions.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/invariants/assertions.py&lt;/code&gt;&lt;/a&gt; ~25 prédicats.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;p&gt;Toutes les assertions suivent le &lt;strong&gt;même contrat&lt;/strong&gt; :&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Soit un prédicat direct &lt;code&gt;(value: T) -&amp;gt; None&lt;/code&gt; qui lève &lt;code&gt;InvariantViolationError&lt;/code&gt; si le contrat n&amp;rsquo;est pas respecté.&lt;/li&gt;&#xA;&lt;li&gt;Soit une &lt;strong&gt;closure&lt;/strong&gt; quand il faut passer un argument de configuration (&lt;code&gt;is_equal_to(cmp)&lt;/code&gt;, etc.).&lt;/li&gt;&#xA;&lt;li&gt;Soit, exceptionnellement, une &lt;strong&gt;higher-order function (HOF)&lt;/strong&gt; comme &lt;code&gt;each&lt;/code&gt;, placée ici par pragmatisme (créer un fichier entier pour ce seul cas serait &lt;em&gt;overkill&lt;/em&gt;).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;tableau-complet&#34;&gt;Tableau complet&lt;a class=&#34;anchor&#34; href=&#34;#tableau-complet&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Assertion&lt;/th&gt;&#xA;          &lt;th&gt;Direct / closure / HOF&lt;/th&gt;&#xA;          &lt;th&gt;Domaine&lt;/th&gt;&#xA;          &lt;th&gt;Description&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_str(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Lève si &lt;code&gt;value&lt;/code&gt; n&amp;rsquo;est pas une &lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_none(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Lève si &lt;code&gt;value is not None&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_not_none(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Lève si &lt;code&gt;value is None&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_equal_to(cmp)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Retourne &lt;code&gt;(value) -&amp;gt; None&lt;/code&gt; qui lève si &lt;code&gt;value != cmp&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_not_equal_to(cmp)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Retourne &lt;code&gt;(value) -&amp;gt; None&lt;/code&gt; qui lève si &lt;code&gt;value == cmp&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_less_than(cmp)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value &amp;lt; cmp&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_less_than_or_equal_to(cmp)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value &amp;lt;= cmp&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_greater_than(cmp)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value &amp;gt; cmp&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_greater_than_or_equal_to(cmp)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value &amp;gt;= cmp&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_positive(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value &amp;gt;= 0&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_not_zero(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value != 0&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_in(elements)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;value in tuple(elements)&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_file(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str | Path&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Path(value).is_file()&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_dir(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str | Path&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Path(value).is_dir()&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_iso_date_string(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;datetime.fromisoformat(value)&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_iso_utc_date_string(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Vérifie ISO + &lt;code&gt;tzinfo == UTC&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_email(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Pas d&amp;rsquo;espace, exactement 1 &lt;code&gt;@&lt;/code&gt;, parts non vides, domaine contient &lt;code&gt;.&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;has_unique_elements(*, key=None)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;closure&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Iterable[Any]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Détecte les doublons (lève &lt;code&gt;DuplicatesError&lt;/code&gt;). Type-strict (&lt;code&gt;1 ≠ True&lt;/code&gt;), supporte les unhashables.&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_empty(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Sized&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;len(value) == 0&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_truthy(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Any&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;bool(value) is True&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;is_valid_filename(value)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;direct&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;str&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Cross-platform : chars interdits, mots réservés Windows, pas leading/trailing space ni dot, longueur ≤ 255&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;each(predicate)&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;HOF&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;Iterable[Any]&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Applique &lt;code&gt;predicate&lt;/code&gt; à chaque élément&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;quelques-implémentations-en-détail&#34;&gt;Quelques implémentations en détail&lt;a class=&#34;anchor&#34; href=&#34;#quelques-impl%c3%a9mentations-en-d%c3%a9tail&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;is_email&#34;&gt;&lt;code&gt;is_email&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#is_email&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;is_email&lt;/span&gt;(value: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Assert that the string is a valid email address (fast check).&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34; &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; value:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; InvariantViolationError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39; must not contain whitespace.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; value&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;count(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;@&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; InvariantViolationError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39; must contain exactly one &amp;#39;@&amp;#39; character.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    local_part, domain_part &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; value&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;split(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;@&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; local_part &lt;span style=&#34;color:#ff79c6&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; domain_part:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; InvariantViolationError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39; must have non-empty local and domain parts.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;.&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; domain_part:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; InvariantViolationError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Domain part of &amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39; must contain at least one &amp;#39;.&amp;#39;.&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Quatre vérifications minimales. Pas de regex RFC5322. C&amp;rsquo;est volontaire : l&amp;rsquo;auteur fait le pari que la validation &lt;em&gt;exhaustive&lt;/em&gt; d&amp;rsquo;un email se fait en envoyant un email, pas en regex.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.04.03 — .otherwise(...) et _any_of</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/03-otherwise-any-of/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/03-otherwise-any-of/</guid>
      <description>&lt;h1 id=&#34;020403otherwise-et-_any_of&#34;&gt;02.04.03 — &lt;code&gt;.otherwise(...)&lt;/code&gt; et &lt;code&gt;_any_of&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020403otherwise-et-_any_of&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Comment Ocarina exprime un &lt;strong&gt;OR logique&lt;/strong&gt; entre deux prédicats sans casser l&amp;rsquo;agrégation d&amp;rsquo;erreurs.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;le-problème&#34;&gt;Le problème&lt;a class=&#34;anchor&#34; href=&#34;#le-probl%c3%a8me&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Cas concret : on veut « &lt;em&gt;&lt;code&gt;age == 18&lt;/code&gt; OR &lt;code&gt;age &amp;lt;= 65&lt;/code&gt;&lt;/em&gt; ». La syntaxe ROP/invariants est strictement chaîne d&amp;rsquo;assertions ; chaque &lt;code&gt;.assert_that()&lt;/code&gt; est un AND. Comment exprimer un OR ?&lt;/p&gt;&#xA;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;otherwise&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, fallback: Predicate[T], &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, msg: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ValidationAssertBlock[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_last_predicate &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; RuntimeError(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;otherwise() must follow assert_that().&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    fallback_with_msg &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; _with_msg(fallback, msg, &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_name)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    combined &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; _any_of(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_last_predicate, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;([&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_otherwise_predicates, fallback_with_msg])&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_steps&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;pop()                    &lt;span style=&#34;color:#6272a4&#34;&gt;# remplace le dernier step&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;add_assertion(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_value, combined, &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_name)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_last_predicate &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; combined&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_otherwise_predicates&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;append(fallback_with_msg)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Guard&lt;/strong&gt;. &lt;code&gt;otherwise()&lt;/code&gt; doit suivre un &lt;code&gt;assert_that()&lt;/code&gt;. Sans prédicat précédent, on lève. (En pratique, le type-checker l&amp;rsquo;interdit déjà : &lt;code&gt;ValidationStartBlock.otherwise&lt;/code&gt; n&amp;rsquo;existe pas.)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Combinaison&lt;/strong&gt;. On combine le prédicat précédent + tous les &lt;code&gt;otherwise&lt;/code&gt; déjà accumulés + le nouveau, via &lt;code&gt;_any_of&lt;/code&gt;. Le résultat est un prédicat composite.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Remplacement du step&lt;/strong&gt;. On &lt;em&gt;pop&lt;/em&gt; le dernier step du &lt;code&gt;_ValidationChain&lt;/code&gt; et on &lt;em&gt;push&lt;/em&gt; le step combiné. Sans ça, on aurait à la fois le prédicat original ET le combiné, donc l&amp;rsquo;erreur originale resurgirait.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;_any_of&#34;&gt;&lt;code&gt;_any_of&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#_any_of&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_any_of&lt;/span&gt;[T](&lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;predicates: _PredicateWithMsg[T]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; _PredicateWithMsg[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_try_predicate&lt;/span&gt;(p: _PredicateWithMsg[T], value: T) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; InvariantViolationError &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;try&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            p(value)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;except&lt;/span&gt; InvariantViolationError &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; exc:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; exc&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;else&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;combined&lt;/span&gt;(value: T) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        errors &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; []&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; p &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; predicates:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            error &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; _try_predicate(p, value)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; error &lt;span style=&#34;color:#ff79c6&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;                              &lt;span style=&#34;color:#6272a4&#34;&gt;# ✅ un prédicat passe → tout passe&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            errors&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;append(error)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        formatted_error_messages &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;  | &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;  | &amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;join(&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;(e) &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; e &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; errors)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        msg &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;All predicates failed for value &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;!r}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;» At least one of the following conditions must be satisfied:&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;formatted_error_messages&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;raise&lt;/span&gt; InvariantViolationError(msg)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; _PredicateWithMsg(combined)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;First success wins&lt;/strong&gt; : dès qu&amp;rsquo;un prédicat passe, &lt;code&gt;combined&lt;/code&gt; retourne &lt;code&gt;None&lt;/code&gt; (succès).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Si tous échouent&lt;/strong&gt; : on construit un message &lt;em&gt;agrégé&lt;/em&gt; listant &lt;strong&gt;toutes&lt;/strong&gt; les raisons d&amp;rsquo;échec, séparées par &lt;code&gt;|&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Le résultat est un &lt;code&gt;_PredicateWithMsg&lt;/code&gt;&lt;/strong&gt; : on peut le rebrancher dans la chaîne comme n&amp;rsquo;importe quel autre prédicat.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;L&amp;rsquo;agrégation est récursive&lt;/strong&gt; : si on a 3 &lt;code&gt;otherwise()&lt;/code&gt; successifs, le 3e combine &lt;code&gt;(P_orig OR P_otherwise1 OR P_otherwise2 OR P_otherwise3)&lt;/code&gt; en un seul prédicat.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;exemple&#34;&gt;Exemple&lt;a class=&#34;anchor&#34; href=&#34;#exemple&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;validate(age, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;age&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_equal_to(&lt;span style=&#34;color:#bd93f9&#34;&gt;18&lt;/span&gt;), msg&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Must be 18 or at most 65&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;otherwise(is_less_than_or_equal_to(&lt;span style=&#34;color:#bd93f9&#34;&gt;65&lt;/span&gt;), msg&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Must be 18 or at most 65&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raise_if_invalid()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;Si &lt;code&gt;age == 18&lt;/code&gt; : &lt;code&gt;is_equal_to(18)&lt;/code&gt; passe → OK.&lt;/li&gt;&#xA;&lt;li&gt;Si &lt;code&gt;age == 30&lt;/code&gt; : &lt;code&gt;is_equal_to(18)&lt;/code&gt; lève, &lt;code&gt;is_less_than_or_equal_to(65)&lt;/code&gt; passe → OK.&lt;/li&gt;&#xA;&lt;li&gt;Si &lt;code&gt;age == 70&lt;/code&gt; : les deux lèvent → &lt;code&gt;InvariantViolationError&lt;/code&gt; avec :&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;age: All predicates failed for value 70.&#xA;» At least one of the following conditions must be satisfied:&#xA;  | age: Must be 18 or at most 65&#xA;  | age: Must be 18 or at most 65&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(Le doublon de message vient du fait qu&amp;rsquo;on a passé le même &lt;code&gt;msg=&lt;/code&gt; aux deux ; c&amp;rsquo;est volontaire ici, on &lt;em&gt;veut&lt;/em&gt; exprimer une intention unique.)&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.04.04 — .then(...) et chain_validations(...)</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/04-then-chain-of-validations/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/04-then-chain-of-validations/</guid>
      <description>&lt;h1 id=&#34;020404then-et-chain_validations&#34;&gt;02.04.04 — &lt;code&gt;.then(...)&lt;/code&gt; et &lt;code&gt;chain_validations(...)&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020404then-et-chain_validations&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Deux mécanismes différents pour composer plusieurs validations en une seule.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;thennew_value&#34;&gt;&lt;code&gt;.then(new_value)&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#thennew_value&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;then&lt;/span&gt;[U](&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, new_value: U, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, name: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ValidationStartBlock[U]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; ValidationStartBlock(new_value, &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;_chain, name)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Change le type&lt;/strong&gt; de &lt;code&gt;T&lt;/code&gt; à &lt;code&gt;U&lt;/code&gt;. Le checker accepte le passage à un autre type.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Conserve la chaîne&lt;/strong&gt; (&lt;code&gt;self._chain&lt;/code&gt;). Toutes les assertions précédentes restent dans la chaîne ; toutes les suivantes &lt;em&gt;s&amp;rsquo;ajoutent&lt;/em&gt; à la chaîne.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Bascule le &lt;code&gt;name&lt;/code&gt;&lt;/strong&gt; : c&amp;rsquo;est le &lt;code&gt;name&lt;/code&gt; fourni à &lt;code&gt;.then()&lt;/code&gt; qui sera utilisé pour les assertions ajoutées ensuite (et non celui du block précédent).&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;cas-dusage-canonique&#34;&gt;Cas d&amp;rsquo;usage canonique&lt;a class=&#34;anchor&#34; href=&#34;#cas-dusage-canonique&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;validate(user, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;user&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_not_none)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;then(user&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;email, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;user.email&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_email)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;then(user&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;age, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;user.age&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_positive)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_less_than_or_equal_to(&lt;span style=&#34;color:#bd93f9&#34;&gt;120&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raise_if_invalid()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Lecture : on valide &lt;code&gt;user&lt;/code&gt; puis &lt;code&gt;user.email&lt;/code&gt; puis &lt;code&gt;user.age&lt;/code&gt;. Si &lt;em&gt;toutes&lt;/em&gt; ces validations passent : OK. Si ne serait-ce qu&amp;rsquo;&lt;em&gt;une seule&lt;/em&gt; échoue, toutes les erreurs sont agrégées dans le &lt;code&gt;AggregateInvariantViolationError&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.04.05 — BusinessInvariantValidator vs FrameworkInvariantValidator</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/05-business-vs-framework-validator/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/05-business-vs-framework-validator/</guid>
      <description>&lt;h1 id=&#34;020405businessinvariantvalidator-vs-frameworkinvariantvalidator&#34;&gt;02.04.05 — &lt;code&gt;BusinessInvariantValidator&lt;/code&gt; vs &lt;code&gt;FrameworkInvariantValidator&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020405businessinvariantvalidator-vs-frameworkinvariantvalidator&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Deux factories — strictement identiques en code — qui existent &lt;strong&gt;uniquement pour signaler l&amp;rsquo;intention&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_BaseCustomInvariantValidator&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    @staticmethod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;create&lt;/span&gt;[T](&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        value: T,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        build_chain: Callable[[ValidationStartBlock[T], T], ValidationAssertBlock[T]],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ValidationAssertBlock[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; _create_business_invariant_validator(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            value, name, &lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt; chain: build_chain(chain, value)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;BusinessInvariantValidator&lt;/span&gt;(_BaseCustomInvariantValidator):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Factory for creating business domain invariant validators.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    Use this for domain-specific validation logic (e.g., &amp;#34;user must be adult&amp;#34;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    &amp;#34;order total must match items&amp;#34;).&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;FrameworkInvariantValidator&lt;/span&gt;(_BaseCustomInvariantValidator):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Factory for creating framework-level invariant validators.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    Use this for technical/framework validation logic (e.g., &amp;#34;config must be valid&amp;#34;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    &amp;#34;test structure must be correct&amp;#34;).&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_create_business_invariant_validator&lt;/span&gt;[T](&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value: T,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    build_chain: ValidationChainBuilder[T],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ValidationAssertBlock[T]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    start_block &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; validate(value, name&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;name)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; build_chain(start_block)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;pourquoi-deux-classes&#34;&gt;Pourquoi deux classes ?&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-deux-classes&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;code&gt;BusinessInvariantValidator&lt;/code&gt; &lt;em&gt;signale&lt;/em&gt; qu&amp;rsquo;on valide une règle métier ; &lt;code&gt;FrameworkInvariantValidator&lt;/code&gt;, une règle technique du framework. À la lecture du code, l&amp;rsquo;intention saute aux yeux.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Grep&lt;/strong&gt;. On peut chercher &lt;code&gt;FrameworkInvariantValidator.create&lt;/code&gt; pour trouver &lt;em&gt;tous les invariants framework&lt;/em&gt;. Idem pour les business rules côté projet.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Maintenabilité&lt;/strong&gt;. Si l&amp;rsquo;on veut un jour ajouter du comportement (log différent, stack-trace différente) à l&amp;rsquo;une ou à l&amp;rsquo;autre, on peut le faire sans refactor de tous les call-sites.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;usages&#34;&gt;Usages&lt;a class=&#34;anchor&#34; href=&#34;#usages&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Invariant&lt;/th&gt;&#xA;          &lt;th&gt;Catégorie&lt;/th&gt;&#xA;          &lt;th&gt;Où il vit&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate_workers_amount&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/workers.py&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate_test_runners_ids&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/oc_test_runners_ids.py&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate_test_runners_names&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/oc_test_runners_names.py&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate_test_suites_names&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/oc_test_suites_names.py&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate_test_campaigns_names&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/oc_test_campaigns_names.py&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;validate_test_cycle_name&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Framework&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;src/ocarina/custom_invariants/testing/oc_test_cycles_names.py&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;em&gt;Exemple métier&lt;/em&gt; : « &lt;em&gt;l&amp;rsquo;username doit être l&amp;rsquo;un de la whitelist&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Business&lt;/td&gt;&#xA;          &lt;td&gt;À écrire côté projet utilisateur&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;em&gt;Exemple métier&lt;/em&gt; : « &lt;em&gt;la date de réservation doit être future et &amp;lt; 1 an&lt;/em&gt; »&lt;/td&gt;&#xA;          &lt;td&gt;Business&lt;/td&gt;&#xA;          &lt;td&gt;À écrire côté projet utilisateur&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;pattern-complet&#34;&gt;Pattern complet&lt;a class=&#34;anchor&#34; href=&#34;#pattern-complet&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# src/ocarina/custom_invariants/testing/workers.py&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; ocarina.dsl.invariants.assertions &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; is_not_zero, is_positive&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; ocarina.dsl.invariants.validate &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; FrameworkInvariantValidator&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_workers_amount_chain&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    chain: ValidationStartBlock[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;],&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ValidationAssertBlock[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    msg &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Value Error: Number of workers must be at least 1 (got: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;value&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;).&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; chain&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_positive, msg&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;msg)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(is_not_zero, msg&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;msg)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validate_workers_amount&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;, workers_amount: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;, name: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ValidationAssertBlock[&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Validate that workers amount is at least 1.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; FrameworkInvariantValidator&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;create(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        workers_amount, name, _workers_amount_chain&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol&gt;&#xA;&lt;li&gt;Une fonction privée &lt;code&gt;_workers_amount_chain(chain, value) -&amp;gt; ValidationAssertBlock&lt;/code&gt; qui définit le &lt;em&gt;quoi&lt;/em&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Une fonction publique &lt;code&gt;validate_workers_amount(*, workers_amount, name)&lt;/code&gt; qui fournit l&amp;rsquo;API et délègue à &lt;code&gt;FrameworkInvariantValidator.create&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Utilisation côté &lt;code&gt;TestSuite.run&lt;/code&gt; : &lt;code&gt;validate_workers_amount(...).execute().raise_if_invalid()&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;quelle-factory-pour-quel-cas&#34;&gt;Quelle factory pour quel cas&lt;a class=&#34;anchor&#34; href=&#34;#quelle-factory-pour-quel-cas&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Holy Book (extrait du chapitre « &lt;em&gt;Extensibilité&lt;/em&gt; ») :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.04.06 — Hiérarchie d&#39;erreurs des invariants</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/06-invariant-errors/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/04-invariants/06-invariant-errors/</guid>
      <description>&lt;h1 id=&#34;020406hiérarchie-derreurs-des-invariants&#34;&gt;02.04.06 — Hiérarchie d&amp;rsquo;erreurs des invariants&lt;a class=&#34;anchor&#34; href=&#34;#020406hi%c3%a9rarchie-derreurs-des-invariants&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/errors.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/invariants/errors.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;InvariantViolationError (base — sous-classe d&amp;#39;Exception)&#xA;├── DuplicatesError                  (levée par `has_unique_elements`)&#xA;└── AggregateInvariantViolationError (levée par `_ValidationResult.raise_if_invalid`)&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;invariantviolationerror&#34;&gt;&lt;code&gt;InvariantViolationError&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#invariantviolationerror&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;InvariantViolationError&lt;/span&gt;(Exception):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Base exception raised when an invariant is violated.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une simple sous-classe d&amp;rsquo;&lt;code&gt;Exception&lt;/code&gt; sans logique propre. Tous les prédicats lèvent cette classe (ou une sous-classe).&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;try&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    validate(value)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_that(predicate)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raise_if_invalid()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;except&lt;/span&gt; InvariantViolationError &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; e:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logger&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;error(&lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Validation failed: &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;e&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;… attrape &lt;strong&gt;toutes&lt;/strong&gt; les violations en une seule clause.&lt;/p&gt;&#xA;&lt;h2 id=&#34;duplicateserror&#34;&gt;&lt;code&gt;DuplicatesError&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#duplicateserror&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;DuplicatesError&lt;/span&gt;(InvariantViolationError):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(&lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;, duplicates: Sequence[Any], message: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-style:italic&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;duplicates &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; duplicates&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        msg &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; message &lt;span style=&#34;color:#ff79c6&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Duplicate elements detected:&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;join(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f1fa8c&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34; - &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;{&lt;/span&gt;d&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; d &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; duplicates&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;super&lt;/span&gt;()&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;__init__&lt;/span&gt;(msg)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;Stocke la liste des doublons dans &lt;code&gt;.duplicates&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Génère un message « &lt;em&gt;Duplicate elements detected:&lt;code&gt;\n&lt;/code&gt; - apple&lt;code&gt;\n&lt;/code&gt; - banana&lt;/em&gt; » par défaut.&lt;/li&gt;&#xA;&lt;li&gt;Accepte un &lt;code&gt;message=&lt;/code&gt; custom.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Levée par &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/invariants/assertions.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;has_unique_elements&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
