# Formules

Vérifiez les réponses entre elles, à même le formulaire, en une seule ligne. `end_date > start_date` indique tout de suite
au visiteur que ses dates sont inversées, sans serveur à vous et sans code à héberger.

## En écrire une

Ouvrez une question dans le **générateur**, puis **Validations**, et choisissez **Formule**. Écrivez ce qui doit être vrai,
et le message à afficher quand ce ne l’est pas. Le générateur vous relit la formule en mots et vous indique quoi corriger
pendant que vous tapez.

```txt
end_date > start_date
```

Pour afficher ou masquer une question, ouvrez **Conditions** et choisissez **Avancé : écrire une formule**. Une formule peut
côtoyer vos autres règles dans un groupe.

```txt
quantity * unit_price >= 100
```

Ajoutez quelques essais sous **Essayer** pour voir la réponse avant de publier. Ils restent pour la prochaine personne qui
modifie le formulaire, et les visiteurs ne les voient jamais.

## Ce que vous pouvez écrire

| Vous écrivez | Cela veut dire |
| --- | --- |
| `a + b`, `a - b`, `a * b`, `a / b`, `a % b` | Arithmétique |
| `a == b`, `a != b`, `a < b`, `a <= b`, `a > b`, `a >= b` | Comparer deux éléments |
| `country in ["CA", "US"]` | L’une de ces valeurs |
| `a and b`, `a or b`, `not a` | Combiner |
| `contact.phone` | Une question dans un groupe |
| `items[].price`, `items[0].price` | Une colonne d’un groupe répété, ou une rangée |
| `` `first-name` `` | Une clé avec un trait d’union, un chiffre au début, ou le nom d’un mot ou d’une fonction |

Le texte s’écrit entre guillemets doubles ou simples, et les guillemets courbes d’un téléphone fonctionnent aussi. Il n’y a
ni affectation, ni boucle, ni moyen d’atteindre quoi que ce soit hors des réponses de ce formulaire.

## Fonctions

| Groupe | Fonctions |
| --- | --- |
| Vérifications | `luhn(text)`, `isEmpty(value)`, `isEmail(text)`, `isUrl(text)`, `isPostalCode(text, "CA")`, `isPhone(text)` |
| Texte | `len`, `lower`, `upper`, `trim`, `contains`, `startsWith`, `endsWith`, `concat` |
| Nombres | `round(number, places)`, `floor`, `ceil`, `abs`, `min`, `max` |
| Listes | `count`, `sum`, `contains`, `unique` |
| Dates | `date(text)`, `today("America/Toronto")`, `daysBetween(from, to)`, `dateOf(moment, "America/Toronto")`, `year`, `month`, `day` |
| Logique | `if(condition, a, b)`, `coalesce(a, b, ...)` |

`luhn` indique qu’un numéro est bien formé, rien de plus. Ne recueillez pas de numéros de carte dans un formulaire.

## Quand une question n’a pas de réponse

Une question sans réponse est inconnue : une question facultative n’échoue donc jamais parce qu’elle est vide.

| Cas | Résultat |
| --- | --- |
| Arithmétique avec une question sans réponse | Inconnu |
| `<`, `<=`, `>`, `>=` avec une question sans réponse | Inconnu |
| `==`, `!=`, `in` | Donnent toujours une réponse. `a == null` est vrai quand `a` est sans réponse, et `a != "CA"` aussi |
| Une formule de validation qui finit inconnue | Passe. C’est **Obligatoire** qui exige une réponse |
| Une condition qui finit inconnue | Fausse. La règle ne s’applique pas encore |
| Une division par zéro | Inconnu |

Ainsi, `country == "CA"` comme validation échoue sur une réponse vide. Pour la permettre, écrivez `isEmpty(country) or country == "CA"`.

## Nombres et dates

Les nombres sont des décimales exactes : `0.1 + 0.2 == 0.3` est vrai partout, jusqu’à 30 chiffres significatifs et 12
décimales. Les dates sont des dates de calendrier, donc aucun fuseau horaire ne peut les déplacer. `today` exige un fuseau
horaire nommé, parce que l’aujourd’hui d’une entreprise et celui d’un visiteur ne sont pas toujours le même jour. On ne peut
pas comparer une date à un nombre, ni une date à du texte tant que `date(field)` n’a pas lu ce texte comme une date.

## Qui peut lire une formule

Le navigateur des visiteurs exécute la formule : une erreur paraît dès que la personne quitte le champ, et n’importe qui peut la lire. Le générateur
vous avertit quand elle contient une valeur qui pourrait compter, comme un code. **N’y mettez pas de secret.**

Pour une vérification qui ne doit pas montrer sa logique, activez **Vérifier seulement à l’envoi, et garder privé**. Rien
n’en arrive au navigateur, et l’erreur vient de notre serveur à l’envoi du formulaire. Même là, quelqu’un peut essayer des
valeurs et lire les réponses : pour une vraie liste de secrets, faites la vérification sur votre propre serveur.

Afficher ou masquer une question doit fonctionner pendant la saisie, donc ces formules ne peuvent pas être privées. Une
formule qui lit une liste est vérifiée seulement à l’envoi, et ne peut ni afficher ni masquer une question.

Nos serveurs refont chaque vérification, quoi qu’en dise le navigateur : sauter une formule dans le navigateur ne permet donc
pas de la contourner. Une formule ne peut pas lire un champ de mot de passe, un destinataire, un expéditeur ni un identifiant
d’accès.

## Quand une question change

Si vous renommez une question ou la déplacez dans une section, le générateur met vos formules à jour à l’enregistrement. Si
vous supprimez une question qu’une formule lit, le générateur nomme ce qui en dépend, et la formule s’affiche **À corriger**
jusqu’à ce que vous la répariez. On ne peut pas publier un formulaire avec une formule à corriger, et les personnes sur une
version précédente gardent cette version.

La condition d’un destinataire ne fait pas partie de la version publiée. Une formule à cet endroit lit chaque réponse telle
qu’elle est arrivée : si elle nomme une question renommée, elle la lit comme sans réponse pour les envois faits avant le
changement.

## Limites

Une formule peut compter 1 000 caractères, 500 parties et 32 niveaux, et un formulaire peut en contenir jusqu’à 64 Ko. Une
liste compte ses 1 000 premiers éléments, et au-delà, la formule est inconnue. Pour aller plus loin, séparez-la en deux
règles.

## Pour aller plus loin

### Les formules en JSON

Une formule va partout où va une condition, et comme validateur d’une règle. Envoyez seulement `text` : le serveur écrit le
reste. Une règle faite uniquement de formules n’a pas besoin de `when`.

```json
{
  "validationRules": [
    {
      "id": "dates",
      "then": [
        {
          "path": "end_date",
          "validator": {
            "type": "formula",
            "formula": { "text": "end_date > start_date" },
            "code": "END_BEFORE_START",
            "message": "The end date has to be after the start date."
          }
        }
      ]
    }
  ],
  "visibilityRules": [
    {
      "id": "budget",
      "path": "budget",
      "default": "hidden",
      "state": "visible",
      "when": { "formula": { "text": "quantity * unit_price >= 100" } }
    }
  ]
}
```

Un schéma public envoie une formule sous la forme `{ "v": 1, "tree": { ... } }`, sans texte. `v` est la version du langage
dans laquelle la formule a été enregistrée, et elle garde son sens pour de bon. Les fonctions et les résultats d’une version
ne changent pas.

### Dans votre code

`validateFormeriePublicFields` exécute les formules d’un schéma une fois l’évaluateur chargé, et les formulaires dessinés par
la trousse le chargent d’eux-mêmes.

```ts
import { loadFormerieFormulas, validateFormeriePublicFields } from "@formerie/client";

await loadFormerieFormulas();
const result = validateFormeriePublicFields(schema, answers);
```

Le paquet livre la spécification du langage dans `@formerie/client/conformance/formula-v1.json`. Chaque entrée s’exécute dans
le navigateur et sur nos serveurs.

### Avec MCP

`get_form_schema` donne la grammaire et les fonctions. `validate_form_definition` vérifie une formule sans enregistrer, et
`formulaSamples` l’essaie avec des réponses, sans rien conserver. `update_form` en enregistre une dans `definitionPatch`. Avec
MCP ou l’API, une formule qui ne passe pas est refusée avec son code `FORMULA_`, au lieu d’être gardée en brouillon à corriger
plus tard. Consultez [Codes d’erreur](/fr/guides/error-codes/).
