Catalogue des statements
Chaque élément du tableau statements est un statement. Ce document catalogue les champs, le comportement et le résultat des 25 types de statement. Chaque emplacement de valeur suit les règles des Expressions de valeur (référence, littéral, JsonLogic, mappage de locale), avec deux exceptions : le pattern de Regex et le key de Cache (voir Regex et Cache).
Résumé des statements
| Catégorie | type | Résumé en une ligne |
|---|---|---|
| Écritures de ressource | ResourceCreate | Créer un Content/Media (publication facultative) |
ResourceUpdate | Remplacement intégral des champs de Content/Media (tout champ/locale non fourni est supprimé) | |
ResourcePatch | Fusion partielle des champs de Content/Media (uniquement les champs/locales spécifiés ; un null littéral supprime) | |
ResourceDelete | Supprimer (Draft/Archived uniquement ; si Published, dépublier d'abord) | |
ResourcePublish / ResourceUnpublish | Publier / dépublier | |
ResourceArchive / ResourceUnarchive | Archiver / désarchiver | |
| Lectures de ressource | ResourceRead | Lecture d'un seul élément par id |
ResourceFind | Premier élément correspondant via filtre (null si aucun) | |
ResourceForEach | Parcourt en interne les ressources correspondant au filtre et exécute onEach pour chaque élément | |
ResourceCount | Compte uniquement le nombre d'éléments correspondant au filtre (sans les lire) | |
| Externe | Http | Appel HTTP externe ({ status, body }) |
EmailSend | Envoie un courriel via un EmailAccount enregistré | |
| Variables | SetVar | Déclarer/mettre à jour une variable à portée script |
| Cache | Cache | Lire, écrire ou supprimer dans un cache à courte durée de vie propre à ce Script |
| Parsing de valeur | ParseJson | Parser un texte JSON en valeur (objet, tableau, scalaire) et la lier |
| Signature et texte | Signature | Vérifier que le code de signature reçu est identique à celui calculé avec la clé secrète (Boolean) |
Hash | Calculer un condensé sans clé (chaîne) | |
Regex | Appliquer une expression régulière. Correspondance (Boolean) ou groupes de capture (tableau) | |
| Flux de contrôle | If | Branchement conditionnel |
Loop | Itération (foreach / while / counted) | |
Parallel | Exécution simultanée de branches | |
Return | Renvoyer un résultat et terminer prématurément | |
Try | Gestion des exceptions (catch/finally) |
Tout statement Content qui ne désigne pas sa cible par id doit indiquer le Content Type qu'il traite. Pour
ResourceFind,ResourceForEachetResourceCount,contentTypeest obligatoire dès lors queresourcevaut"Content". Il n'existe pas de requête Content qui traverse tout un Space.ResourceCreateindique lui aussi le Content Type à créer. Un Media ne porte pas de périmètre, puisqu'il n'en existe qu'un seul jeu pour tout le Space ; et les statements qui désignent leur cible par id (ResourceRead,ResourceUpdate,ResourcePatch,ResourceDeleteainsi que les statements de publication et d'archivage) n'ont pas besoin de périmètre, puisqu'ils onttarget.
Les appels cycliques sont limités à 3. En activant
propagateEventssur les statements d'écriture de ressources ci-dessus (ResourceCreate,ResourceUpdate,ResourcePublish, etc.), dont la valeur par défaut est désactivée, ces écritures déclenchent des événements de modification, et ces événements peuvent exécuter à nouveau un Script via un Webhook. Une telle chaîne (Script → événement → Webhook → Script → …) se poursuit au maximum 3 fois. Au-delà, la plateforme l'interrompt automatiquement pour éviter les boucles infinies.
Champs communs
{ "type": "<StatementType>", "name": "<facultatif, unique dans le script>", /* ...champs spécifiques au type... */ }type: le discriminant. L'une des valeurs du tableau ci-dessus (obligatoire).name: facultatif. Lorsqu'il est défini, le résultat est lié au contexte à/<name>, de sorte que les statements suivants peuvent le référencer via{ /<name>/... }. On l'omet si l'on n'utilise pas le résultat.- Règles de nom de liaison :
nameest une clé posée telle quelle à la racine du contexte ; elle est donc validée à l'enregistrement. Elle ne peut contenir que des lettres ASCII (a-z, A-Z), des chiffres,_et-(afin de pouvoir servir de clé JSON Pointer, tout autre caractère ou un nom vide est refusé), ne peut pas coïncider avec une racine réservée (payload,rawPayload,headers,vars,error,now), et doit être unique au sein d'un même Script. En cas de violation du format, d'usage d'un mot réservé ou de doublon, l'enregistrement est refusé.
Forme des références d'entité
Les références d'entité comme contentType et target sont unifiées en une seule forme : { "sys": { "id": <expression de valeur> } }. Seul sys.id est nécessaire ; le type cible est déduit de resource (sys.type et sys.targetType sont omis).
contentType.sys.idest généralement un littéral (par ex."ct_post").target.sys.idest généralement une expression de valeur{ /ptr }(résolue à l'exécution ; par ex.{ /payload/sys/id }).
resource
Les statements de la famille ressource indiquent le type de cible avec resource: "Content" | "ContentType" | "Media" | "ServiceUser".
Seul ResourceCount accepte Content Type. L'indiquer dans un autre statement fait refuser l'enregistrement. Créer ou modifier le Content Type lui-même relève de CMA, et non d'un Script.
ServiceUser (le membre inscrit au produit) est en lecture seule. Seuls les trois statements de lecture (ResourceRead, ResourceFind, ResourceForEach) acceptent cette valeur ; l'indiquer dans un statement d'écriture fait refuser l'enregistrement (voir Erreurs). Les règles sont traitées dans Lecture de l'annuaire des membres.
Écritures de ressource
Chaque statement d'écriture possède propagateEvents (par défaut false). Le mettre à true fait que cette écriture déclenche un événement de modification, et des traitements en aval comme les Webhook s'exécutent. Par défaut, elle n'en déclenche aucun (une écriture système silencieuse).
ResourceCreate
Crée un Content ou un Media. Content et Media partagent le modèle fields, et les valeurs sont des mappages de locale.
| Champ | S'applique à | Description |
|---|---|---|
resource | Commun | "Content" ou "Media" (obligatoire) |
contentType | Content | Le Content Type à créer ({ sys: { id } }). Obligatoire pour Content |
fields | Commun | Le mappage de champs { "<field>": { "<locale>": valeur } }. Chaque champ renseigné requiert le bucket de la locale par défaut. Les clés de Content suivent la définition du Content Type ; les clés de Media sont fixes (title, description, file) |
locale | Commun | (Commodité) Si fourni, enveloppe automatiquement chaque valeur de fields en { <locale>: valeur } |
publish | Commun | Publie après l'écriture (exposé sur CDA/ACDA). Par défaut true |
Mediafile: la valeurfields.file.{locale}est une instruction d'ingestion{ "source": <expression de valeur>, "encoding": "url"|"base64" }(les deux sont obligatoires). Sur une écriture qui inclut un fichier, c'est le moteur qui procède à l'ingestion (téléchargement siurl; décodage, puis téléversement et traitement sibase64). Cette ingestion ne déclare aucun temps : elle sort donc du budget de base de 30 secondes (voir Budget de temps), et elle n'est pas comptée dans le plafond d'appels externes. On peut aussi créer un Media sans fichier (fileless). Sipublish:truemais qu'il n'y a pas de fichier ou que le traitement est incomplet, l'étape de publication échoue ; sipublish:false, il resteDraft.- Résultat (liaison de
name) : la ressource créée.{ /<name>/sys/id },{ /<name>/fields/<field>/<locale> }.
// Content
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"fields": { "title": { "en-US": "{ /payload/fields/title }" } }, "publish": true, "name": "post" }
// Media. file est une instruction d'ingestion
{ "type": "ResourceCreate", "resource": "Media",
"fields": {
"title": { "en-US": "{ /payload/fields/prompt }" },
"file": { "en-US": { "source": "{ /gen/body/data/0/url }", "encoding": "url" } }
}, "name": "img" }ResourceUpdate
Remplace intégralement les champs du Content ou Media cible (PUT). Ce que l'on passe dans fields devient le nouvel ensemble de champs, et tout champ et locale absent ici est supprimé. Pour n'en changer qu'une partie, on utilise ResourcePatch.
| Champ | Description |
|---|---|
resource | "Content" ou "Media" |
target | La cible ({ sys: { id } }, obligatoire). L'id est généralement { /ptr } |
fields | L'ensemble complet des champs à écrire. Les valeurs sont des mappages de locale. Comme il s'agit d'un remplacement intégral, tout champ et locale absent ici est supprimé. Pour un Media, file est une instruction d'ingestion (voir ResourceCreate ci-dessus) ; les fichiers listés sont toujours réingérés, et les fichiers des locales non fournies sont supprimés |
locale | (Commodité) Enveloppe automatiquement fields |
version | (Facultatif) Une expression de valeur (Int). Verrouillage optimiste. Si fournie, la mise à jour ne s'effectue que si elle correspond au sys.version actuel de la cible ; en cas de non-correspondance, elle abandonne avec une erreur de conflit de version (interceptable avec Try). Si omise, aucune vérification (last-write-wins) |
publish | Republie après la mise à jour. Par défaut true |
Si l'on utilise Update pour ne modifier que les métadonnées d'un Media, le file étant absent, tous les fichiers sont supprimés (puisqu'il s'agit d'un remplacement intégral). Pour une modification partielle, on utilise impérativement ResourcePatch.
{ "type": "ResourceUpdate", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "title": { "en-US": "Hello", "ko-KR": "안녕" }, "status": { "en-US": "published" } } }ResourcePatch
Fusionne partiellement les champs du Content ou Media cible (PATCH). Écrase uniquement les champs (et les locales qu'ils contiennent) passés dans fields, et laisse intacts les champs et locales non mentionnés. La forme des valeurs, locale, version et publish sont identiques à ResourceUpdate.
| Champ | Description |
|---|---|
resource | "Content" ou "Media" |
target | La cible ({ sys: { id } }, obligatoire). L'id est généralement { /ptr } |
fields | Les champs à écraser. Les valeurs sont des mappages de locale. Met à jour uniquement les champs et buckets de locale spécifiés (le reste est conservé). Si une valeur est un null littéral, ce couple (champ, locale) est supprimé. Pour un Media, file est une instruction d'ingestion (voir ResourceCreate ci-dessus) |
locale | (Commodité) Enveloppe automatiquement fields |
version | (Facultatif) Identique à ResourceUpdate (verrouillage optimiste) |
publish | Republie après la mise à jour. Par défaut true |
- Supprimer une locale ou un fichier spécifique : on donne un
nulllittéral comme valeur. Par ex. :"title": { "fr-FR": null }(supprime le titre fr-FR),"file": { "en-US": null }(supprime le fichier en-US). Une expression de valeur qui s'évalue à null au moment de l'exécution n'est pas une suppression mais une erreur (seul un null littéral supprime). - Donner une instruction d'ingestion au
filed'un Media remplace le fichier de cette locale. Si l'on ne fournit pas de fichier, il est conservé.
// +1 sur viewCount(en-US) uniquement. title, les autres locales, etc. sont conservés tels quels
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "viewCount": { "en-US": { "$+": [ "{ /payload/fields/viewCount }", 1 ] } } } }ResourceDelete
Supprime la cible. Seuls les statuts Draft et Archived peuvent être supprimés. Si l'élément est Published ou Changed, la suppression est refusée : il faut donc d'abord ResourceUnpublish. Pour un Media, la suppression est refusée pendant que le fichier est en cours de traitement. Il ne dépublie pas automatiquement (comme CMA/ACMA).
| Champ | Description |
|---|---|
resource | "Content" ou "Media" |
target | La cible ({ sys: { id } }, obligatoire) |
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }ResourcePublish, ResourceUnpublish, ResourceArchive, ResourceUnarchive
Contrôle indépendamment l'état de publication et d'archivage de la cible. Les quatre partagent les mêmes champs. La précondition de statut de chaque opération est identique à CMA/ACMA. ResourcePublish est impossible depuis Archived et exige que le traitement du fichier soit terminé. ResourceUnpublish n'est possible que depuis Published ou Changed, ResourceArchive que depuis Draft, et ResourceUnarchive que depuis Archived.
| Champ | Description |
|---|---|
resource | "Content" ou "Media" |
target | La cible ({ sys: { id } }, obligatoire) |
version | (Facultatif) Une expression de valeur (Int). Verrouillage optimiste. Si fournie, l'opération ne s'effectue que si elle correspond au sys.version actuel |
{ "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }
{ "type": "ResourceUnpublish", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }
{ "type": "ResourceArchive", "resource": "Media", "target": { "sys": { "id": "{ /m/sys/id }" } } }Lectures de ressource
ResourceRead et ResourceFind lisent une ressource et la lient comme valeur, tandis que ResourceCount en compte seulement le nombre. Aucun des trois ne change d'état (pas de propagateEvents). Pour ResourceForEach aussi, la requête elle-même est une lecture, mais si onEach contient un statement d'écriture de ressource, cette écriture s'exécute pour chaque élément et modifie l'état.
Les quatre statements (ResourceRead, ResourceFind, ResourceForEach, ResourceCount) déterminent tous, via from (Current par défaut), quelle copie stockée lire. Current est le dernier brouillon que voit le studio de contenu (la valeur que lisent CMA/ACMA), et Published est l'instantané publié (celui que distribuent CDA/ACDA, la valeur au moment de la dernière publication). Un ServiceUser n'étant pas publié, il n'accepte que Current (voir Lecture de l'annuaire des membres).
ResourceFind, ResourceForEach et ResourceCount activent ou désactivent en outre la Recherche avancée (Advanced Search) via advanced (par défaut true). En l'absence d'indication, elle est activée. Réservée au Content, elle est ignorée pour les lectures de Media et de ServiceUser. Lorsqu'elle est activée, where peut utiliser les opérateurs regex, near et within ainsi que la recherche en texte intégral (sur un champ LongText dont la recherche en texte intégral est activée, eq trouve aussi les éléments qui contiennent la valeur, par correspondance partielle et approchée), et order peut trier par fields.*. Lorsqu'elle est désactivée, ces trois opérateurs sont rejetés, eq sur du texte est une correspondance exacte, et prefix ainsi que les opérateurs de comparaison et de liste fonctionnent indépendamment de la recherche avancée. Un élément qui vient d'être créé ou modifié met un court instant (environ 1 seconde) à apparaître dans la recherche avancée, si bien que la requête de recherche avancée qui suit immédiatement peut ne pas le renvoyer. Comme elle est activée par défaut, ce délai concerne toutes les requêtes tant que l'on ne met pas advanced à false. Pour lire sans attendre un élément qui vient d'être écrit, utilisez ResourceRead par id (le stockage principal, sans délai d'apparition) ou interrogez par le sys.id renvoyé par l'écriture.
Le createdBy: ":self" de where signifie « uniquement ce qu'a créé l'utilisateur qui appelle maintenant ». Il est en revanche inutilisable dans un Script qui autorise l'appel anonyme (anonymousCallEnabled). Dans ce cas, :self se résout non pas sur l'appelant mais sur l'auteur, ce qui ouvrirait silencieusement les ressources de l'auteur ; une telle définition est donc refusée à l'enregistrement (voir Appel anonyme).
Dans where et order, un champ de contenu s'écrit fields.<field> (le nom seul n'est pas reconnu). À fields.<field>, le moteur applique automatiquement la locale par défaut du Space, si bien qu'on n'y ajoute pas de locale directement. Les fields.status et fields.slug des exemples ci-dessous interrogent donc directement la locale par défaut. Ce n'est que pour interroger une locale précise (non par défaut) qu'on la spécifie avec fields.<field>.<locale> (par ex. fields.title.ko-KR). sys.* (comme sys.createdAt) et createdBy (:self) s'écrivent tels quels, sans fields.. Les règles détaillées figurent dans La locale dans where et order des Expressions de valeur.
Lecture de l'annuaire des membres (ServiceUser)
ResourceRead, ResourceFind et ResourceForEach acceptent "ServiceUser" dans resource et lisent alors l'annuaire des membres de ce Space (ResourceCount ne l'accepte pas ; voir ResourceCount ci-dessous). On s'en sert pour vérifier à qui appartient une commande, ou pour trouver un membre par son adresse e-mail et passer son sys.id au statement suivant. Les règles ci-dessous sont communes aux trois statements.
- Seule la lecture est possible.
ResourceCreate,ResourceUpdate,ResourcePatch,ResourceDeleteainsi que les statements de publication et d'archivage n'acceptent pas"ServiceUser", et une telle définition est refusée à l'enregistrement. Ce n'est pas quelque chose qui s'ouvre en ajoutant une permission : dans un Script, il n'existe tout simplement aucun moyen de modifier un membre, si bien que le refus n'est pas une erreur de permission mais un statement mal écrit. - L'enregistrement exige que l'auteur ait la permission sur l'annuaire des membres. La vérification ne passe pas par les mappages de permissions comme pour Content et Media : on regarde si le
settingsdu SpaceRole de l'auteur contientSETTING_SERVICE_LOGIN(ouSETTING_ALL). L'annuaire des membres est en effet, sur tous les autres chemins également, une ressource que gouvernent les paramètres du Space. À défaut, l'enregistrement est refusé (voir le modèle de sécurité). fromn'accepte queCurrent. Un membre n'étant pas une ressource publiée, donnerPublishedfait échouer l'exécution.contentTypeetadvancedsont ignorés. L'annuaire des membres ne se répartit pas par Content Type (il n'en existe qu'un seul pour tout le Space), et la recherche avancée est réservée au Content.- Le
sys.emaildewheren'accepte que les opérateurs de la famille de l'égalité exacte (eq,ne,in,nin). L'adresse d'un membre étant stockée chiffrée, une comparaison d'ordre ou unprefixn'a aucun sens. Donner un autre opérateur ne renvoie pas silencieusement 0 élément : l'exécution échoue. - Le résultat est la ressource ServiceUser elle-même. On la référence via
{ /<name>/sys/id }ou{ /<name>/nickname }. Sa structure est traitée dans la référence ServiceUser. Pour envoyer un courriel au membre trouvé, on ne sort pas son adresse : on passe sonsys.idautoServiceUserdeEmailSend(le moteur résolvant l'adresse juste avant l'envoi, l'adresse du membre n'entre pas dans l'espace de variables du Script).
// Trouve un seul membre par son adresse e-mail. null s'il n'y en a pas
{ "type": "ResourceFind", "resource": "ServiceUser",
"where": { "sys.email": { "eq": "{ /payload/fields/email }" } }, "name": "member" }ResourceRead
Une lecture d'un seul élément par id (get-by-id). Le résultat lie la ressource entière au nom.
| Champ | Description |
|---|---|
resource | "Content", "Media" ou "ServiceUser" |
target | La cible ({ sys: { id } }). L'id est une expression de valeur |
from | (Facultatif) Current (par défaut, dernier brouillon) ou Published (instantané publié). Pour ServiceUser, uniquement Current |
- Résultat : c'est la ressource elle-même qui est liée. Si vous avez donné un
nameà ce statement, référencez-la directement via{ /<name>/sys/id }et{ /<name>/fields/<field>/<locale> }(avec le"name": "order"de l'exemple ci-dessous,{ /order/sys/id }). Ce n'est pas une liste, donc aucun index de tableau n'intervient. - Si la cible n'existe pas, cela génère une erreur. On peut l'envelopper dans un
Trypour la gérer.
{ "type": "ResourceRead", "resource": "Content",
"target": { "sys": { "id": "{ /payload/fields/orderId }" } }, "name": "order" }ResourceFind
Lit le premier élément correspondant, via un filtre. S'il n'y en a aucun, c'est null. À utiliser pour trouver un enregistrement par une clé métier unique (slug, email, sku).
| Champ | Description |
|---|---|
resource | "Content", "Media" ou "ServiceUser" |
contentType | Le Content Type dans lequel chercher ({ sys: { id } }). Obligatoire pour Content. Ignoré pour Media et ServiceUser |
where | Le filtre ({ "<field>": { "<op>": <valeur> } }). Les opérateurs sont ceux de la liste des opérateurs (regex/near/within nécessitent advanced). createdBy: ":self" pris en charge. Pour le sys.email d'un ServiceUser, uniquement eq, ne, in et nin (voir Lecture de l'annuaire des membres) |
order | Le tri qui détermine le « premier » lorsque plusieurs correspondent (par ex. "-sys.createdAt") |
from | (Facultatif) Current (par défaut, dernier brouillon) ou Published (instantané publié). Pour ServiceUser, uniquement Current |
advanced | (Facultatif) Exécuter via la recherche avancée (Advanced Search). Content uniquement (Media et ServiceUser ignorés). Par défaut true. Voir la note Lectures de ressource ci-dessus |
- Résultat : lie la première ressource correspondante au
namede ce statement. On la référence directement via{ /<name>/fields/<field>/<locale> }. Comme elle vautnullen l'absence de résultat, on branche selon l'existence avec{ "==": [ "{ /<name> }", null ] }(le schéma classique du find-then-upsert).
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }, "name": "found" }ResourceForEach
Parcourt en interne les ressources correspondant au filtre et exécute onEach pour chaque élément. C'est un statement destiné à effectuer une opération sur chaque élément plutôt qu'à constituer une collection à utiliser comme valeur. On l'utilise pour les tâches répétitives : publication en masse de brouillons, modification en masse de Content correspondant à une condition, envoi ou synchronisation de chaque élément vers l'extérieur. Pour ne lire qu'un seul élément, on utilise ResourceRead (par id) ou ResourceFind (par filtre).
| Champ | Description |
|---|---|
resource | "Content", "Media" ou "ServiceUser" (obligatoire) |
contentType | Le Content Type du périmètre de parcours ({ sys: { id } }). Obligatoire pour Content. Ignoré pour Media et ServiceUser |
where | Le filtre ({ "<field>": { "<op>": <valeur> } }). Sa sémantique est identique au where de ResourceFind (y compris la contrainte sur le sys.email d'un ServiceUser). Les opérateurs sont ceux de la liste des opérateurs (regex/near/within nécessitent advanced). createdBy: ":self" pris en charge |
order | Le tri (par ex. "sys.createdAt,sys.id"). En son absence, l'ordre par défaut de la plateforme |
from | Current (par défaut, dernier brouillon) ou Published (instantané publié). Pour ServiceUser, uniquement Current |
advanced | Parcours via la recherche avancée (Advanced Search). Content uniquement (Media et ServiceUser ignorés). Par défaut true. Voir la note Lectures de ressource ci-dessus |
limit | (Facultatif, ≥1) Le plafond du nombre total d'éléments traités (ce n'est pas une taille de page). En son absence, le parcours va jusqu'au plafond de la plateforme (10 000 éléments) |
name | (Facultatif) Le nom auquel lier l'élément courant. Il est relié à nouveau à chaque itération et se référence via { /<name> } à l'intérieur de onEach (même durée de vie que le name d'un Loop ; après la fin du parcours, le dernier élément reste lié). On l'omet si l'on ne référence pas l'élément |
onEach | Le tableau de statements enfants à exécuter pour chaque élément (obligatoire) |
- Il ne lie pas de collection (
foreach, pasmap). Il n'y a ni{ items, next }ni cursor. On ne récupère pas le résultat du parcours comme valeur :onEachs'exécute pour chaque élément. Si l'on a besoin d'une liste, on l'accumule soi-même avecSetVar. Si seul le décompte est nécessaire, on utiliseResourceCount. - Même sans
limit, ce n'est pas un parcours infini. En son absence, il parcourt jusqu'au plafond de la plateforme (10 000 éléments), et s'il atteint ce plafond alors qu'il reste des correspondances, c'est un échec (afin de ne pas signaler un succès en laissant des éléments non traités). À l'inverse, atteindre lelimitdéclaré est un arrêt voulu, donc une terminaison normale. Unlimitsupérieur au plafond est refusé à l'enregistrement. - Il n'y a pas de cursor. Aller au bout est un succès ; une interruption en cours de route (dépassement du temps d'horloge ou du quota, échec non géré dans
onEach) est un échec, et l'erreur désigne sur quel élément et pourquoi l'échec s'est produit. La reprise s'exprime par l'auteur avec ses propres données (en laissantwheresur « non traité » et en marquant l'achèvement à la fin deonEach, une réexécution reprend à partir de ce qui reste). - Il compte dans le budget de temps comme une multiplication. Le temps que ce statement déclare est le temps déclaré par
onEachmultiplié par le nombre d'éléments traités (limit, ou 10 000 à défaut), voir Budget de temps. Comme c'est un statement composite qui possède des enfants, il ne compte pas lui-même dans le budget de leaf d'appels externes ; ce sont les statements d'appel externe à l'intérieur deonEachqui sont comptés dans le budget. onEachpeut contenir, comme tout autre statement, des appels externes (Http,EmailSend) ou l'ingestion de fichier Media (comme lebodyd'unLoop). La raison d'être de ce statement est de traiter une fois par élément le résultat d'une requête sur les ressources.
// Trouve tous les articles à l'état draft et publie chaque élément
{ "type": "ResourceForEach", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"where": { "fields.status": { "eq": "draft" } }, "order": "sys.createdAt,sys.id",
"from": "Current", "advanced": false, "name": "post",
"onEach": [
{ "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /post/sys/id }" } } }
] }ResourceCount
Compte uniquement le nombre d'éléments correspondant au filtre. Comme il ne va pas lire les éléments, on l'utilise lorsqu'on a besoin d'un décompte plutôt que d'une liste. C'est le statement qui convient pour vérifier le stock restant, déterminer si une même valeur existe déjà, ou contrôler si un plafond est dépassé.
| Champ | Description |
|---|---|
resource | "Content" ou "ContentType" (obligatoire). Media et ServiceUser ne peuvent pas être comptés ; les indiquer ainsi fait refuser l'enregistrement |
contentType | Le Content Type du périmètre à compter ({ sys: { id } }). Obligatoire pour Content. Ignoré lorsqu'on compte des Content Type (il n'en existe qu'un seul jeu pour tout le Space) |
where | Le filtre. Sa sémantique est identique au where de ResourceFind. Tous les éléments correspondants sont comptés |
from | (Facultatif) Current (par défaut, dernier brouillon) ou Published (instantané publié) |
advanced | (Facultatif) Exécuter via la recherche avancée (Advanced Search). Content uniquement (ignoré lorsqu'on compte des Content Type). Par défaut true. Voir la note Lectures de ressource ci-dessus |
name | (Facultatif) Le nom auquel lier le décompte |
- Résultat : lie le nombre d'éléments correspondants au
namede ce statement. On le référence via{ /<name> }pour les comparaisons et les branchements. - Il ne renvoie pas les éléments. Si vous avez besoin des éléments, utilisez
ResourceFind(premier élément correspondant) ouResourceForEach(exécution pour chaque élément). - Ne comptez pas en parcourant avec
ResourceForEachpour obtenir un décompte. Un parcours réserve le budget de temps multiplié par le nombre d'éléments (voir Budget de temps), et il échoue s'il atteint le plafond de la plateforme alors qu'il reste des correspondances. Si seul le décompte est nécessaire, ce statement le règle en une seule fois. orderetlimitn'existent pas : compter ne demande aucun ordre, et tout ce qui correspond est compté.
// Compte le nombre de commentaires attachés à cet article
{ "type": "ResourceCount", "resource": "Content", "contentType": { "sys": { "id": "ct_comment" } },
"where": { "fields.postId": { "eq": "{ /payload/sys/id }" } }, "name": "commentCount" }Externe
Http
Appelle un endpoint HTTP externe. S'agissant d'un appel externe, il est compté dans le plafond d'appels externes propre au plan et compte, dans le budget de temps, pour timeoutMs (30 secondes à défaut) × (1 + retry).
| Champ | Description |
|---|---|
method | "GET", "POST", "PUT", "PATCH", "DELETE" |
url | L'URL cible (une expression de valeur ; { /ptr } peut y être inséré) |
headers | [{ "key", "value", "secret"? }]. value est une expression de valeur. Un en-tête secret:true est traité comme réservé à CMA (administrateur) : il n'est pas exposé aux utilisateurs finaux et n'est déchiffré que juste avant l'envoi de la requête. Si vous placez Content-Type ici, body est sérialisé dans ce format (ci-dessous) |
body | Le corps de la requête (une expression de valeur ou du JSON). Le format sous lequel il est envoyé est fixé par l'en-tête Content-Type |
timeoutMs | Le délai d'expiration de cet appel (ms) |
retry | Le nombre de nouvelles tentatives lorsque le statut de la réponse est supérieur ou égal à 400. Par défaut 0 ; le plafond est de 2 |
ignoreStatusCode | Indique s'il faut traiter cet appel comme un échec lorsque le statut final (après les nouvelles tentatives) est supérieur ou égal à 400. Lorsque false (par défaut), il est traité comme un échec et devient une cible de Try/catch. Lorsque true, il n'est pas traité comme un échec et { status, body } est lié tel quel (l'appelant branche lui-même sur status) |
responseType | Sous quelle forme recevoir le corps de la réponse. "Json" (par défaut) le parse en objet ou tableau, "Text" le reçoit comme chaîne |
- Résultat :
{ status, body }. Si vous avez donné unnameà ce statement,{ /<name>/status },{ /<name>/body/... }. La forme debodyest fixée parresponseType. responseTypene s'applique qu'à une réponse en succès. Le corps d'une réponse dont le statut est supérieur ou égal à 400 est lié à des fins de diagnostic quelle que soit la valeur déclarée (la valeur parsée si c'est du JSON, une chaîne sinon).- Si
"Json"est déclaré et que le corps n'est pas du JSON, cet appel échoue (cible deTry/catch). Pour les API qui ne renvoient pas de JSON, recevez le corps en"Text", puis parsez-le avecParseJsonsi vous devez le traiter comme une valeur. "Text"est décodé avec le charset duContent-Typede la réponse, et considéré comme UTF-8 en l'absence de charset. Si le corps est vide,bodyvautnulldans les deux cas.- Limite de taille de réponse : le corps de la réponse est de 10MiB au maximum. S'il dépasse cette taille, cet appel échoue avec une exception et peut être géré comme n'importe quel autre échec d'exécution avec
Try/catch(il s'agit d'un échec basé sur la taille, il n'est donc pas ignoré parignoreStatusCode).
{ "type": "Http", "method": "POST", "url": "https://api.llm.com/v1/gen",
"headers": [ { "key": "Authorization", "value": "Bearer sk-...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 15000, "retry": 1,
"responseType": "Json", "name": "resp" }Le format sous lequel body est envoyé
Le Content-Type placé dans headers fixe le format de sérialisation de body. La comparaison ignore la casse ainsi que les paramètres tels que ;charset=… et ne considère que la partie initiale. En l'absence de cet en-tête, ou lorsque sa valeur est vide, l'envoi se fait en application/json. Cet en-tête n'est ajouté que lorsqu'un body est présent : sans body, l'en-tête inscrit part tel quel. Lorsque la même clé est présente plusieurs fois, seule la première valeur est retenue et les occurrences sont fusionnées en une seule.
Un body que le format déclaré ne peut pas porter est corrigé vers un format capable de le porter avant l'envoi. Il n'arrive jamais que l'en-tête annonce autre chose que le body réellement envoyé.
Voici les combinaisons qui partent dans le format déclaré.
Content-Type déclaré | Forme de body | body envoyé |
|---|---|---|
application/json | N'importe laquelle | JSON |
application/x-www-form-urlencoded | Objet ou tableau | order[id]=A-2481&order[amount]=34000 |
text/plain | Scalaire | La valeur telle quelle |
Autre (text/xml, etc.) | N'importe laquelle | JSON |
Voici les combinaisons corrigées parce que le format déclaré ne peut pas les porter.
Content-Type déclaré | Forme de body | Content-Type réellement envoyé | body envoyé |
|---|---|---|---|
application/x-www-form-urlencoded | Scalaire | text/plain;charset=UTF-8 | La valeur telle quelle |
text/plain | Objet ou tableau | application/json | JSON |
Ces deux lignes indiquent comment la requête part lorsque la paire ne concorde pas, et non comment obtenir le format visé. Lorsque body est assemblé à partir d'une expression de valeur, il peut devenir un scalaire selon le payload au moment de l'exécution, et cette correction se produit alors sans erreur. Si le destinataire conteste le format, corrigez selon votre intention soit la forme de body, soit le Content-Type.
form-urlencoded développe les objets en clés à crochets et les tableaux en index.
body | Clés et valeurs développées |
|---|---|
{ "order": { "id": "A-2481", "amount": 34000 } } | order[id]=A-2481&order[amount]=34000 |
{ "tags": ["outerwear", "winter"] } | tags[0]=outerwear&tags[1]=winter |
{ "items": [{ "sku": "TUMBLER-500" }] } | items[0][sku]=TUMBLER-500 |
{ "memo": null } | memo= |
Les clés et les valeurs partent en UTF-8 avec un encodage-pourcent. Le tableau ci-dessus est présenté sous forme décodée afin de montrer la structure des clés. Même si une valeur contient & ou +, elle n'est pas prise pour un séparateur de paires ni pour une espace : elle est transmise telle quelle.
Cette notation, qui développe l'imbrication en clés à crochets, est une convention largement répandue et non une spécification du format lui-même. Vérifiez si le destinataire reconstitue order[id] en objet imbriqué ; s'il ne le reconstitue pas, composez body avec des clés plates.
{ "type": "Http", "method": "POST", "url": "https://api.example.com/oauth/token",
"headers": [ { "key": "Content-Type", "value": "application/x-www-form-urlencoded" } ],
"body": { "grant_type": "client_credentials", "client_id": "{ /vars/clientId }" },
"name": "token" }EmailSend
Envoie un courriel via un EmailAccount enregistré. Les champs acceptés sont uniquement ceux qui se mappent tels quels sur SMTP/MIME. Il n'y a ni id de modèle, ni envoi programmé, ni extension propre à un fournisseur (si une telle fonctionnalité est nécessaire, on appelle directement l'API du service de courriel concerné avec Http). L'expéditeur (l'adresse d'envoi) ne se définit pas ici : il provient de l'EmailAccount désigné par account.
| Champ | Description |
|---|---|
account | La référence de l'EmailAccount d'envoi ({ sys: { id } }, obligatoire). Généralement un id littéral. Fourni sous forme d'expression de valeur, il est résolu au moment de l'envoi et ne peut donc pas être vérifié à l'enregistrement |
to | L'adresse du destinataire (expression de valeur). On utilise exactement l'un de to ou toServiceUser |
toServiceUser | Désigne le destinataire par une référence ServiceUser ({ sys: { id } } ; son sys.id peut être une expression de valeur). Le moteur résout l'adresse juste avant l'envoi, si bien que l'adresse du membre n'entre pas dans l'espace de variables du Script |
cc | Le tableau des adresses en copie (expression de valeur) |
bcc | Le tableau des adresses en copie cachée (expression de valeur) |
subject | L'objet (expression de valeur, obligatoire) |
body | Le corps (expression de valeur, obligatoire). Toujours envoyé en text/html, on y écrit donc du balisage et non du texte brut (les sauts de ligne deviennent des espaces, < est interprété comme une balise). Les résultats d'expressions de valeur interpolés sont échappés en HTML |
replyTo | (Facultatif) L'en-tête Reply-To (expression de valeur). Peut différer de l'expéditeur (par ex. envoyer depuis un no-reply mais rediriger les réponses vers une adresse de support) |
timeoutMs | (Facultatif, ≥1) Le délai d'expiration de cet envoi (ms). En son absence, la valeur par défaut de la plateforme ; une valeur dépassant le plafond est refusée à l'enregistrement |
- Le total des destinataires est de 50 au maximum. On additionne
to(1 personne),ccetbcc(l'enveloppe SMTP ne distingue pas cc/bcc et tous partent comme destinataires, on les compte donc dans le total). En cas de dépassement, c'est refusé à l'enregistrement et à l'exécution. Pour envoyer à beaucoup de personnes, on envoie un courriel par élément avecResourceForEach+EmailSend. - Il ne lie pas de résultat. Le succès signifie seulement que « le fournisseur a accepté le courriel » : il n'y a pas de valeur à renvoyer, donc il n'accepte pas de
name. Il ne réessaie pas non plus (le courriel n'est pas idempotent : réessayer après un échec ambigu produirait un double envoi ; c'est pourquoi il ne suit pas leretrydeHttp). Un échec est levé (throw) et se gère avec lecatchd'unTry. - C'est un appel externe. Il est compté dans le plafond d'appels externes propre au plan et compte, dans le budget de temps, pour un seul
timeoutMs(10 secondes à défaut) : ne réessayant pas, il ne multiplie pas par un nombre de tentatives commeHttp. Il peut s'utiliser à l'intérieur deonEachd'unResourceForEach(la forme standard de l'envoi en nombre).
{ "type": "EmailSend", "account": { "sys": { "id": "eml_orders" } },
"to": "{ /order/fields/email/en-US }",
"subject": "Commande bien reçue (n° de commande { /order/sys/id })",
"body": "<p>Votre commande a bien été reçue. Nous vous préviendrons dès que la livraison commencera.</p>",
"replyTo": "support@my-shop.example" }Variables
SetVar
Déclare ou met à jour une variable mutable à portée du script. On la référence via { /vars/<var> } (JsonLogic n'a pas de déclaration de variable, c'est donc fourni sous forme de statement).
| Champ | Description |
|---|---|
var | Le nom de la variable. Référencée via { /vars/<var> } |
value | Une expression de valeur. Elle peut se référencer elle-même pour accumuler |
{ "type": "SetVar", "var": "total", "value": 0 }
{ "type": "SetVar", "var": "total", "value": { "$+": [ "{ /vars/total }", "{ /row/qty }" ] } } // accumulation
{ "type": "SetVar", "var": "ids", "value": { "$merge": [ "{ /vars/ids }", [ "{ /row/sys/id }" ] ] } } // collecte dans un tableauMise en cache
Cache
Lit et écrit dans un cache à courte durée de vie propre à ce Script. C'est l'emplacement où l'on garde quelques secondes une valeur qu'il serait dommage d'aller rechercher à chaque fois, comme le résultat d'un appel externe, afin de la réutiliser à l'appel suivant. N'étant pas un appel externe, il n'est pas compté dans le nombre d'appels externes par définition et ne déclare aucun temps dans le budget de temps.
| Champ | Description |
|---|---|
action | L'une des valeurs "Set" (écrire), "Get" (lire), "Delete" (supprimer) (obligatoire) |
key | La clé de cache (obligatoire). Un littéral, et non une expression de valeur (voir ci-dessous). 128 caractères au maximum ; au-delà, l'enregistrement est refusé |
value | La valeur à stocker (Set uniquement) |
ttl | La durée de vie du cache (Set uniquement, en secondes). Entre 1 et 30 ; en son absence, 5 |
defaultValue | La valeur que Get lie lorsqu'il n'y a pas de données en cache (Get uniquement). En son absence, null |
name | Le nom qui reçoit le résultat. Obligatoire pour Get (si la valeur lue n'a nulle part où aller, il n'y a aucune raison de la lire). Set lie la valeur stockée et Delete lie le fait que la suppression a eu lieu ; il est facultatif dans les deux cas |
- On n'écrit que les champs qui correspondent à l'opération. Écrire un
ttlsur unGet, ou undefaultValuesur unSet, fait refuser l'enregistrement. - Une donnée absente et une donnée expirée ne se distinguent pas. Dans les deux cas, c'est
defaultValuequi est lié. Il en va de même lorsque l'on a stockénull. - La portée du stockage est ce seul Script. Un autre Script du même Space ne voit pas les données de celui-ci, même s'il emploie la même clé de cache. Modifier ou supprimer ce Script fait disparaître toutes ses données.
keyest un littéral. Laisser une clé de cache venue de la requête choisir la donnée reviendrait à laisser l'appelant décider ce qui est lu, et un Script qui garde une donnée par membre livrerait la valeur d'un membre à un autre. C'est pourquoi un{ /pointer }présent danskeyn'est ni remplacé par une valeur, ni employé au caractère près : l'enregistrement lui-même est refusé.- Il ne peut pas être placé dans une itération. Si un
Cachefigure dans le bloc d'unLoopou d'unResourceForEach, l'enregistrement est refusé. C'est que le plafond de quantité ci-dessous ne constitue plus aucune limite à l'intérieur d'une itération : une donnée étant écrite à chaque tour, le nombre de statements inscrits dans la définition et le nombre de clés de cache réellement utilisées divergent. - On peut en placer 5 au maximum par définition (imbriqués inclus, toutes opérations confondues). Au-delà, l'enregistrement est refusé.
- La valeur stockée ne dépasse pas 10 240 octets (10KiB). Au-delà, ce statement échoue (status 422). Comme tout autre échec d'exécution, il se traite localement avec
Try/catch.
// Réutilise le taux de change pendant 30 secondes.
{ "type": "Cache", "action": "Get", "name": "cached", "key": "rates" }
// Si une valeur est déjà en réserve, on la renvoie telle quelle, sans appel externe
{ "type": "If", "condition": { "!!": [ "{ /cached }" ] },
"then": [ { "type": "Return", "value": "{ /cached }" } ] }
{ "type": "Http", "name": "fetched", "method": "GET", "url": "https://api.example.com/rates" }
{ "type": "Cache", "action": "Set", "key": "rates", "value": "{ /fetched/body }", "ttl": 30 }
{ "type": "Return", "value": "{ /fetched/body }" }
// Jette avant son expiration la valeur que l'on gardait
{ "type": "Cache", "action": "Delete", "key": "rates" }Parsing de valeur
ParseJson
Parse un texte JSON en la valeur qu'il désigne et la lie à un nom. Utilisez-le pour un corps reçu de Http avec responseType: "Text", pour une chaîne JSON arrivée dans le payload, ou pour du JSON conservé sous forme de chaîne dans un champ. Ce n'est pas un appel externe : il n'est donc pas compté dans le plafond d'appels externes et ne déclare aucun temps dans le budget de temps.
| Champ | Description |
|---|---|
name | Le nom qui reçoit la valeur parsée (obligatoire). Il est facultatif sur les autres statements, mais obligatoire ici. Le statement ne fait rien d'autre que lier son résultat : sans nom, il n'a donc aucun effet |
value | Le texte JSON à parser (expression de valeur, obligatoire). Pointez vers une valeur d'une étape précédente, comme dans { /resp/body }, ou écrivez le texte JSON tel quel en littéral (une { à l'intérieur du littéral n'est pas lue comme un modèle { pointeur }) |
- Résultat : la valeur parsée elle-même. Un objet reste un objet, un tableau reste un tableau, et une valeur unique comme
42ou"a"est parsée également. Ensuite, vous adressez l'intérieur avec{ /<name>/... }. - Une valeur déjà parsée est liée telle quelle. Lorsque
valuese résout en autre chose qu'une chaîne, il n'y a pas de texte à parser, et cette valeur est liée en l'état. - Un
{ /pointer }présent dans le texte parsé n'est pas résolu à nouveau. Même si une chaîne reçue de l'extérieur contient une expression comme{ /payload/... }, elle n'est pas remplacée par une valeur et reste du texte. nullcouvre deux cas distincts. Si le texte à parser est le seul motnull, c'est normal et le résultat vautnull. En revanche, si l'emplacement pointé parvalueest vide et qu'il n'y a aucune valeur, il n'y a rien à parser et le statement échoue.- Échec : lorsque
valuese résout sans valeur ou uniquement sur des espaces, et lorsque le texte n'est pas du JSON. Traitez-le avecTry/catchcomme tout autre échec d'exécution ; le message d'erreur emporte le texte qu'il a tenté de parser. - Il compte pour un statement dans le nombre de statements par définition, mais n'a aucun rapport avec le plafond d'appels externes ni avec la limite de
SetVar.
// 1) Une API qui ne renvoie pas de JSON : recevoir en Text, puis parser
{ "type": "Http", "method": "GET", "url": "https://api.partner.example/v1/quote",
"responseType": "Text", "name": "resp" },
{ "type": "ParseJson", "name": "quote", "value": "{ /resp/body }" },
// 2) Parser une chaîne JSON arrivée dans le payload
{ "type": "ParseJson", "name": "spec", "value": "{ /payload/fields/specJson }" }Vérification de signature et traitement de texte
Ce sont les statements qui vérifient la signature qu'un prestataire de paiement a envoyée par webhook, et qui déballent la chaîne dans laquelle cette signature est emballée. Tous trois étant des calculs et non des appels externes, ils ne sont pas comptés dans le plafond d'appels externes et ne déclarent aucun temps dans le budget de temps ; comme ils n'ont pas d'emplacement de données, ils n'ont rien à voir avec la règle du préfixe $. Un exemple complet qui combine les trois figure dans la vérification de signature de webhook du cookbook.
Les trois statements ont tous une limite sur la longueur de la valeur résolue. Il s'agit non pas de la longueur de l'expression mais de la longueur de la valeur qu'elle désigne (les seize caractères de { /rawPayload } peuvent désigner plusieurs dizaines de KB), et un dépassement fait échouer l'exécution, ce qui se traite avec Try. Les valeurs exactes sont rassemblées dans Limites de longueur des valeurs.
Signature
Vérifie que le code de signature reçu est identique à celui produit avec secret, et lie la réponse comme valeur Boolean. C'est avec ce statement qu'on vérifie la signature qu'un prestataire de paiement (PG, MoR) envoie par webhook.
| Champ | Description |
|---|---|
name | Le nom qui reçoit le résultat de la vérification (obligatoire). { /<name> } vaut true ou false. Vérifier sans utiliser le résultat équivaut à ne pas vérifier, il n'est donc pas omissible |
algorithm | Le hachage avec lequel produire le code (obligatoire). SHA1, SHA256, SHA384, SHA512 |
secret | La clé secrète partagée avec le correspondant (expression de valeur, obligatoire) |
secretEncoding | Dans quelle notation secret est écrit. Utf8 (par défaut, clé texte), Hex, Base64. Laisser en texte une clé délivrée en hex ou en base64 en fait une autre clé : un code plausible est produit, mais il ne correspondra jamais |
value | Le message sur lequel calculer le code (expression de valeur, obligatoire). Il doit être identique au caractère près aux octets que le correspondant a signés : c'est donc généralement { /rawPayload }, ou bien { /rawPayload } précédé de l'horodatage que le fournisseur a joint dans l'en-tête |
expected | Le code envoyé par l'appelant (expression de valeur, obligatoire). Par ex. { /headers/x-signature } |
- Résultat : un
Boolean. On écrit ensuite{ /<name> }tel quel dans la condition d'unIf. values'écrit avec/rawPayload, et non avec le/payloadparsé. Refaire une chaîne à partir du payload parsé normalise les espaces, la notation des nombres et les échappements, si bien qu'on ne retrouve pas les octets signés par le correspondant (voir Racines de contexte).- Il n'y a pas de champ pour préciser la notation de sortie.
algorithmfixe la longueur en octets du code et, pour une même longueur, les chaînes hex et base64 n'ont pas de longueur commune : le moteur restitue donc les octets sans qu'on lui indique dans quelle notation le correspondant a envoyé le code. Pour la même raison, il ne distingue ni la casse de l'hex, ni base64 de base64url (avec ou sans padding). - La distinction entre échec et
falsese fait selon qui fournit la valeur.- Si
expectedest absent ou si le code ne correspond pas, le résultat vaut simplementfalse: ce n'est pas un échec. Signaler séparément l'absence d'en-tête et la non-correspondance du code reviendrait à apprendre à l'expéditeur laquelle des deux est en cause. - Si
valueest vide, le calcul se fait sur un message vide. Un corps vide est aussi un objet de signature. - Si
secretest absent ou n'est pas dans la notation déclarée parsecretEncoding, c'est un échec. Des trois valeurs, c'est la seule qui vienne de l'auteur lui-même. Le message d'échec n'emporte nisecretnivalue.
- Si
- La limite de
valueest de 65 536 caractères (sur la valeur résolue). Elle est calée sur la taille des corps de webhook que les fournisseurs envoient réellement. - La comparaison détermine l'égalité à temps constant (constant-time). Le nombre d'octets de tête qui correspondaient ne fuit pas par le temps de réponse.
secretn'est pas stocké chiffré. Contrairement ausecret: trued'un en-têteHttp(stocké chiffré, puis déchiffré juste avant l'envoi), il reste tel qu'il est écrit dans la définition : sa valeur est donc visible pour les rôles capables de lire ce Script. Un membre (ServiceUser) ne peut pas lire la définition d'un Script (l'écriture et la consultation sont réservées à CMA).
// Un fournisseur qui signe le corps entier
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
"secret": "whsec_9f2c1b7ae4", "value": "{ /rawPayload }",
"expected": "{ /headers/x-webhook-signature }" }
// Un fournisseur qui délivre la clé en base64
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
"secret": "aGVsbG8td2VlZ2xvbw==", "secretEncoding": "Base64",
"value": "{ /rawPayload }", "expected": "{ /headers/webhook-signature }" }Hash
Produit le condensé de value et le lie comme chaîne dans la notation fixée par encoding. Il sert à reproduire les schémas de signature qui, au lieu d'un HMAC, « concatènent quelques champs et une clé secrète puis calculent un SHA256 ».
| Champ | Description |
|---|---|
name | Le nom qui reçoit le condensé (obligatoire) |
algorithm | MD5, SHA1, SHA256, SHA384, SHA512 (obligatoire). MD5 est là pour reproduire les anciens schémas qui l'exigent, ce n'est pas une valeur à choisir pour une signature nouvelle |
value | Le message à condenser (expression de valeur, obligatoire) |
encoding | La notation du résultat. Hex (par défaut), HexUpper, Base64, Base64Url |
- Il n'y a pas de champ
secret. La place de la clé variant d'un schéma à l'autre (au début, à la fin, au milieu), écrire la clé directement dansvaluepermet d'exprimer toutes ces places. - Résultat : une chaîne. Pour la comparer au code envoyé par le correspondant, on écrit
{ "==": [ "{ /<name> }", "{ /headers/... }" ] }. Contrairement à la comparaison à temps constant deSignature, cette comparaison est une égalité ordinaire. - Si
valuese résout sans valeur ou uniquement sur des espaces, c'est un échec (puisqu'il s'agit de l'expression de l'auteur lui-même). - La limite de
valueest de 128 caractères. C'est un emplacement destiné à quelques champs concaténés, donc bien plus étroit que celui deSignature. S'il faut calculer sur le corps entier d'un webhook, on utiliseSignature.
// SHA256(numéro de commande + montant + merchantKey) en hex majuscule
{ "type": "Hash", "name": "expectedSign", "algorithm": "SHA256", "encoding": "HexUpper",
"value": "{ /payload/orderId }{ /payload/amount }9f2c1b7ae4" }Regex
Applique pattern à value et lie ce que mode demande. Les expressions de valeur n'offrent aucun moyen de découper une chaîne (il n'y a que cat, qui concatène, et in, qui vérifie l'inclusion) : on utilise donc ce statement pour déballer plusieurs valeurs emballées dans un même en-tête, comme t=…,v1=….
| Champ | Description |
|---|---|
name | Le nom qui reçoit le résultat (obligatoire). Avec Capture, on désigne les éléments via { /<name>/1 } |
mode | "Match" lie la correspondance comme Boolean, "Capture" lie la première correspondance comme tableau (obligatoire) |
pattern | L'expression régulière (obligatoire). Un littéral, et non une expression de valeur (voir ci-dessous). Les drapeaux s'écrivent dans le motif, comme (?i). 128 caractères au maximum ; au-delà, l'enregistrement est refusé |
value | Le texte auquel appliquer le motif (expression de valeur, obligatoire). Si la valeur résolue dépasse 10 240 caractères (10KiB), l'exécution échoue |
- Résultat :
Matchdonne unBoolean,Captureun tableau ounull. Dans le tableau, l'index0est la correspondance entière et les groupes de capture commencent à1; un groupe qui n'a pas participé vautnull(et non une chaîne vide, laquelle signifierait qu'il a correspondu). Si le motif n'apparaît pas,Capturene vaut pas un tableau vide maisnull. - Les deux modes posent la même question : « le motif apparaît-il quelque part ? » Si le texte entier doit être égal au motif, on l'ancre avec
^…$. La question est identique afin que deux statements, l'un qui vérifie avecMatchet l'autre qui extrait avecCapture, ne donnent pas des réponses différentes. patternest l'un des deux seuls champs de ce moteur qui ne soient pas des expressions de valeur (l'autre est lekeydeCache). Exécuter tel quel un motif venu de la requête laisserait l'appelant choisir l'expression exécutée, et le retour sur trace des expressions régulières en ferait un moyen de déni de service. C'est pourquoi un{ /pointer }présent dans le motif n'est pas remplacé par une valeur : il fait littéralement partie du motif.- Les motifs sont compilés une seule fois pour toute la définition, au démarrage de l'exécution. Même à l'intérieur d'un
Loopou d'unResourceForEach, ils ne sont pas recompilés à chaque itération, et un motif inutilisable échoue avant que le premier statement n'ait fait quoi que ce soit (gérable avecTry).
// Déballe "t=1492774577,v1=<hex de 64 caractères>" : { /sig/1 } = horodatage, { /sig/2 } = code
{ "type": "Regex", "name": "sig", "mode": "Capture",
"pattern": "^t=(\\d+),v1=([0-9a-f]{64})$", "value": "{ /headers/x-provider-signature }" }
// Vérifie uniquement le format
{ "type": "Regex", "name": "isOrderId", "mode": "Match",
"pattern": "^ORD-\\d{8}-\\d{4}$", "value": "{ /payload/orderId }" }Flux de contrôle
If
Un branchement conditionnel. condition est du JsonLogic, et l'évaluation en vrai ou faux suit les règles de Valeurs vraies et fausses.
| Champ | Description |
|---|---|
condition | JsonLogic (évalué comme un booléen) |
then | Le tableau de statements à exécuter si vrai |
else | (Facultatif) Le tableau de statements à exécuter si faux |
{ "type": "If",
"condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "crédit insuffisant" } } ],
"else": [ /* ... */ ] }Loop
Une itération. On choisit un seul mode : over (foreach), while (condition) ou for (compté). Quel que soit le mode, le moteur impose un plafond d'itérations (pour éviter les boucles infinies). Ce plafond se déclare avec maxIterations et, à défaut de déclaration, c'est celui de la plateforme qui s'applique. Le body peut aussi contenir des appels externes (Http, EmailSend) et l'ingestion de fichier Media, et les statements d'appel externe sont réellement appelés à chaque itération lors de l'exécution. La limite du nombre maximal d'appels externes par définition s'applique toujours.
Il compte dans le budget de temps comme une multiplication. Le temps que ce statement déclare est le temps déclaré par body multiplié par maxIterations (ou 10 000 à défaut), voir Budget de temps. En l'absence d'appel externe dans le body, le temps déclaré vaut 0 : le budget de base de 30 secondes constitue donc la limite effective.
| Champ | Description |
|---|---|
over | foreach : une expression de valeur qui se résout en un tableau |
while | condition : JsonLogic (répète tant que c'est vrai) |
for | compté : { "from", "to", "step"? }. De from à to inclus ; step vaut 1 par défaut |
maxIterations | Le nombre maximal d'itérations (facultatif). À défaut de déclaration, le plafond de la plateforme de 10 000 s'applique, et une valeur supérieure est refusée à l'enregistrement |
name | (Facultatif) Le nom auquel lier l'élément courant (foreach) ou l'index (while·for) ({ /<name> }) |
body | Le tableau de statements du corps de la boucle |
// foreach
{ "type": "Loop", "over": "{ /payload/fields/items }", "name": "item", "maxIterations": 100,
"body": [ { "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_item" } },
"fields": { "name": { "en-US": "{ /item/name }" } } } ] }
// while
{ "type": "Loop", "while": "{ /vars/hasMore }", "maxIterations": 1000, "body": [ /* ... */ ] }
// counted (1..10 step 2)
{ "type": "Loop", "for": { "from": 1, "to": 10, "step": 2 }, "name": "i", "maxIterations": 100, "body": [ /* ... */ ] }Parallel
Exécute les branches simultanément et poursuit après leur jonction. Les références entre branches ne sont pas autorisées (en cas de dépendance, on les place séquentiellement).
| Champ | Description |
|---|---|
branches | Statement[][]. Chaque élément est une branche (un tableau de statements) |
{ "type": "Parallel", "branches": [
[ { "type": "Http", "method": "GET", "url": "https://api.a.com/x", "name": "a" } ],
[ { "type": "Http", "method": "GET", "url": "https://api.b.com/y", "name": "b" } ]
] }Return
C'est le return de la programmation classique. Il renvoie le résultat du Script à l'appelant et se termine normalement à ce point.
| Champ | Description |
|---|---|
value | (Facultatif) L'expression de valeur à renvoyer |
isError | Par défaut false. Si true, value ressort comme le error de la réponse (sinon comme return) |
statusCode | Le code de statut de la réponse. Par défaut 200 |
- Si
Returnn'est jamais atteint, il n'y a pas de valeur de retour. Pour renvoyer un résultat, on précise explicitementvalue. - Comme il s'agit d'une terminaison normale, et non d'une exception ou d'un throw, ce n'est pas une cible de
catch(même à l'intérieur d'unTry, il termine l'intégralité du Script, maisfinallys'exécute tout de même). - Un guard s'exprime aussi avec ce statement. Si l'on place un
Returndans lethend'unIf, il renvoie une valeur lorsque la condition est violée et les statements suivants ne s'exécutent pas. C'est l'un des nombreux usages deReturn.
{ "type": "Return", "value": { "orderId": "{ /order/sys/id }", "status": "paid" }, "statusCode": 201 }
{ "type": "Return", "value": { "reason": "échec du paiement" }, "isError": true, "statusCode": 402 }Try
Gestion des exceptions.
| Champ | Description |
|---|---|
body | Le tableau de statements à tenter |
catch | (Facultatif) S'exécute en cas d'échec de body. Expose { message } sur /error (le statement où l'échec s'est produit n'y figure pas) |
finally | (Facultatif) S'exécute toujours, que ce soit en cas de succès ou d'échec |
- Si
catchla gère, le Script n'est pas interrompu. Seul un échec sanscatchinterrompt le Script (y compris la tentative de compensation). - Ce qui compte comme un « échec », ainsi que les limites de la compensation, sont traités dans Sémantique d'exécution, contraintes et sécurité.
{ "type": "Try",
"body": [ { "type": "Http", "method": "POST", "url": "https://primary.api/gen", "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/text }" } } } ],
"catch": [ { "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "Generation failed" }, "error": { "en-US": "{ /error/message }" } } } ],
"finally": [ /* toujours exécuté */ ] }Documents associés
- Expressions de valeur : les règles de valeur que suivent tous les champs ci-dessus.
- Sémantique d'exécution, contraintes et sécurité : ordre d'exécution, erreurs, contraintes statiques et sécurité.
- Cookbook : des exemples complets qui combinent ces statements.
- Aperçu de Script : la structure de niveau supérieur et le temps alloué à une exécution.
