Statement-Katalog
Jedes Element des statements-Arrays ist ein einzelnes Statement. Dieses Dokument katalogisiert die Felder, das Verhalten und das Ergebnis aller 25 Statement-Typen. Jede Wertstelle folgt den Regeln der Wertausdrücke (Referenz, Literal, JsonLogic, Locale-Map). Es gibt zwei Ausnahmen: pattern bei Regex und key bei Cache (siehe Regex und Cache).
Statement-Zusammenfassung
| Kategorie | type | Einzeilige Zusammenfassung |
|---|---|---|
| Ressourcen schreiben | ResourceCreate | Content/Media erstellen (optional veröffentlichen) |
ResourceUpdate | Vollständige Ersetzung der Content/Media-Felder (nicht angegebene Felder/Locales werden gelöscht) | |
ResourcePatch | Partielle Zusammenführung der Content/Media-Felder (nur die angegebenen Felder/Locales; ein literales null löscht) | |
ResourceDelete | Löschen (nur Draft/Archived; wenn Published, zuerst unpublish) | |
ResourcePublish / ResourceUnpublish | Veröffentlichen / Zurückziehen | |
ResourceArchive / ResourceUnarchive | Archivieren / Wiederherstellen | |
| Ressourcen lesen | ResourceRead | Per id ein einzelnes Element lesen |
ResourceFind | Per Filter das erste passende Element (null, wenn keines) | |
ResourceForEach | Durchläuft die zum Filter passenden Ressourcen intern und führt für jedes Element onEach aus | |
ResourceCount | Zählt nur die Anzahl der zum Filter passenden Elemente (die Elemente werden nicht gelesen) | |
| Extern | Http | Externer HTTP-Aufruf ({ status, body }) |
EmailSend | Versendet über ein registriertes EmailAccount 1 E-Mail | |
| Variablen | SetVar | Script-lokale Variable deklarieren/aktualisieren |
| Zwischenspeicher | Cache | Den kurzlebigen Cache, der nur diesem Script gehört, lesen, schreiben oder entfernen |
| Wert-Parsing | ParseJson | JSON-Text zu einem Wert (Objekt, Array, Skalar) parsen und binden |
| Signatur und Text | Signature | Prüft, ob der empfangene Signaturcode mit dem Code übereinstimmt, der aus dem geheimen Schlüssel erzeugt wird (Boolean) |
Hash | Digest ohne Schlüssel berechnen (String) | |
Regex | Regulären Ausdruck anwenden. Trefferstatus (Boolean) oder Capture-Gruppen (Array) | |
| Ablaufsteuerung | If | Bedingte Verzweigung |
Loop | Iteration (foreach / while / counted) | |
Parallel | Branches gleichzeitig ausführen | |
Return | Ergebnis zurückgeben und vorzeitig beenden | |
Try | Ausnahmebehandlung (catch/finally) |
Ein Content-Statement, das sein Ziel nicht per id angibt, muss immer den Content Type nennen, mit dem es arbeitet. Bei
ResourceFind,ResourceForEachundResourceCountistcontentTypeerforderlich, sobaldresourceden Wert"Content"hat. Eine Content-Abfrage, die den gesamten Space durchquert, gibt es nicht. AuchResourceCreatenennt den Content Type, der erstellt werden soll. Für Media gilt im gesamten Space ein einziger Satz, dort wird also kein Bereich abgesteckt, und die Statements, die ihr Ziel per id angeben (ResourceRead,ResourceUpdate,ResourcePatchundResourceDeletesowie die Statements für Veröffentlichung und Archivierung), habentargetund brauchen deshalb keinen Bereich.
Zyklische Aufrufe sind auf 3 begrenzt. Schalten Sie bei den obigen ressourcenschreibenden Statements (
ResourceCreate,ResourceUpdate,ResourcePublishusw.)propagateEventsein (der Standard ist aus), lösen sie Änderungsereignisse aus, und diese Ereignisse können über einen Webhook erneut ein Script ausführen. Eine solche Kette (Script → Ereignis → Webhook → Script → …) läuft höchstens 3-mal, darüber hinaus wird sie automatisch abgebrochen, um Endlosschleifen zu verhindern.
Gemeinsame Felder
{ "type": "<StatementType>", "name": "<optional, im Script eindeutig>", /* ...typspezifische Felder... */ }type: Der Diskriminator. Einer der Werte aus der Tabelle oben (erforderlich).name: Optional. Wenn gesetzt, wird das Ergebnis unter/<name>an den Kontext gebunden, sodass spätere Statements es als{ /<name>/... }referenzieren können. Weglassen, wenn das Ergebnis nicht verwendet wird.- Regeln für Bindungsnamen:
nameist ein Schlüssel, der direkt auf die Kontextwurzel gelegt wird, und wird daher beim Speichern validiert. Er darf nur lateinische Buchstaben (a-z, A-Z), Ziffern,_und-enthalten (er muss als JSON-Pointer-Schlüssel verwendbar sein, daher wird jedes andere Zeichen sowie ein leerer Name abgelehnt), darf nicht mit einer reservierten Wurzel (payload,rawPayload,headers,vars,error,now) übereinstimmen und muss innerhalb eines Script eindeutig sein. Bei einem Formatverstoß, der Verwendung eines reservierten Worts oder einem Duplikat wird das Speichern abgelehnt.
Form der Entitätsreferenz
Entitätsreferenzen wie contentType und target werden zu einer einzigen Form vereinheitlicht: { "sys": { "id": <Wertausdruck> } }. Nur sys.id wird benötigt; der Zieltyp wird aus resource abgeleitet (sys.type und sys.targetType werden weggelassen).
contentType.sys.idist normalerweise ein Literal (z. B."ct_post").target.sys.idist normalerweise ein{ /ptr }-Wertausdruck (zur Laufzeit aufgelöst, z. B.{ /payload/sys/id }).
resource
Statements der Ressourcen-Familie geben die Zielart mit resource: "Content" | "ContentType" | "Media" | "ServiceUser" an.
Content Type nimmt nur ResourceCount an. Steht er in einem anderen Statement, wird das Speichern abgelehnt. Das Formular selbst zu erstellen oder zu ändern ist nicht Sache eines Script, sondern der CMA.
ServiceUser (die Mitglieder, die sich für das Produkt registriert haben) ist nur lesbar. Nur die drei Lese-Statements (ResourceRead, ResourceFind, ResourceForEach) nehmen diesen Wert an; steht er in einem Schreib-Statement, wird das Speichern abgelehnt (siehe Fehler). Die Regeln werden unter Mitgliederverzeichnis lesen behandelt.
Ressourcen schreiben
Jedes Schreib-Statement hat propagateEvents (Standard false). Wird es auf true gesetzt, löst dieser Schreibvorgang ein Änderungsereignis aus, sodass nachgelagerte Aktionen wie Webhooks ausgeführt werden. Der Standard löst keines aus (ein stiller System-Schreibvorgang).
ResourceCreate
Erstellt Content oder Media. Content und Media teilen sich das fields-Modell, und die Werte sind Locale-Maps.
| Feld | Gilt für | Beschreibung |
|---|---|---|
resource | Gemeinsam | "Content" oder "Media" (erforderlich) |
contentType | Content | Der zu erstellende Content Type ({ sys: { id } }). Bei Content erforderlich |
fields | Gemeinsam | Feld-Map { "<field>": { "<locale>": Wert } }. Jedes befüllte Feld erfordert den Standard-Locale-Bucket. Content-Schlüssel folgen der Content Type-Definition, Media-Schlüssel sind fest (title, description, file) |
locale | Gemeinsam | (Komfort) Wenn angegeben, wird jeder Wert in fields automatisch als { <locale>: Wert } verpackt |
publish | Gemeinsam | Nach dem Schreiben veröffentlichen (Anzeige über CDA/ACDA). Standard true |
Mediafile: Der Wert vonfields.file.{locale}ist eine Ingest-Anweisung{ "source": <Wertausdruck>, "encoding": "url"|"base64" }(beide erforderlich). Bei einem Schreibvorgang, der eine Datei enthält, führt die Engine den Ingest durch (beiurlwird heruntergeladen, beibase64wird dekodiert, danach wird hochgeladen und verarbeitet). Dieser Ingest deklariert keine Zeit und geht daher vom Grundbudget von 30 Sekunden ab (siehe Zeitbudget), und auf das Limit für externe Aufrufe wird er nicht angerechnet. Es lässt sich auch ein Media ohne Datei (fileless) erstellen. Beipublish:true, aber ohne Datei oder mit unfertiger Verarbeitung, tritt im Veröffentlichungsschritt ein Fehler auf; beipublish:falsebleibt esDraft.- Ergebnis (
name-Bindung): die erstellte Ressource.{ /<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 ist eine Ingest-Anweisung
{ "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
Führt eine vollständige Ersetzung der Felder des Ziel-Content oder -Media durch (PUT). Was in fields übergeben wird, wird genau zum neuen Feldsatz, und alle hier nicht vorhandenen Felder und Locales werden gelöscht. Um nur einen Teil zu ändern, verwenden Sie ResourcePatch.
| Feld | Beschreibung |
|---|---|
resource | "Content" oder "Media" |
target | Das Ziel ({ sys: { id } }, erforderlich). Die id ist normalerweise { /ptr } |
fields | Der vollständige Satz zu schreibender Felder. Die Werte sind Locale-Maps. Da es sich um eine vollständige Ersetzung handelt, werden hier nicht vorhandene Felder und Locales entfernt. Bei Media ist file eine Ingest-Anweisung (siehe ResourceCreate oben). Aufgeführte Dateien werden immer erneut ingestiert, und Dateien nicht angegebener Locales werden gelöscht |
locale | (Komfort) Automatisches Verpacken von fields |
version | (Optional) Wertausdruck (Int). Optimistisches Sperren. Wenn angegeben, wird die Aktualisierung nur dann ausgeführt, wenn sie mit der aktuellen sys.version des Ziels übereinstimmt; bei Nichtübereinstimmung bricht sie mit einem Versionskonflikt-Fehler ab (mit Try abfangbar). Ohne Angabe erfolgt keine Prüfung (last-write-wins) |
publish | Nach der Aktualisierung erneut veröffentlichen. Standard true |
Wenn Sie bei einem Media Update verwenden, um nur die Metadaten zu ändern, fehlt file, und alle Dateien werden gelöscht (da es eine vollständige Ersetzung ist). Für partielle Änderungen verwenden Sie unbedingt ResourcePatch.
{ "type": "ResourceUpdate", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "title": { "en-US": "Hello", "ko-KR": "안녕" }, "status": { "en-US": "published" } } }ResourcePatch
Führt eine partielle Zusammenführung der Felder des Ziel-Content oder -Media durch (PATCH). Es überschreibt nur die in fields übergebenen Felder (und die darin enthaltenen Locales) und lässt alle nicht erwähnten Felder und Locales unverändert. Wertform, locale, version und publish sind identisch mit ResourceUpdate.
| Feld | Beschreibung |
|---|---|
resource | "Content" oder "Media" |
target | Das Ziel ({ sys: { id } }, erforderlich). Die id ist normalerweise { /ptr } |
fields | Die zu überschreibenden Felder. Die Werte sind Locale-Maps. Aktualisiert nur die angegebenen Felder und Locale-Buckets (der Rest bleibt erhalten). Ist ein Wert ein literales null, wird dieses (Feld, Locale) gelöscht. Bei Media ist file eine Ingest-Anweisung (siehe ResourceCreate oben) |
locale | (Komfort) Automatisches Verpacken von fields |
version | (Optional) Identisch mit ResourceUpdate (Optimistisches Sperren) |
publish | Nach der Aktualisierung erneut veröffentlichen. Standard true |
- Eine bestimmte Locale oder Datei löschen: Geben Sie als Wert ein literales
nullan. Beispiel:"title": { "fr-FR": null }(löscht den fr-FR-Titel),"file": { "en-US": null }(löscht die en-US-Datei). Ein Wertausdruck, der zur Laufzeit zu null ausgewertet wird, ist keine Löschung, sondern ein Fehler (nur ein literales null löscht). - Übergeben Sie bei Media
fileeine Ingest-Anweisung, wird die Datei dieser Locale ersetzt. Geben Sie keine Datei an, bleibt sie erhalten.
// nur +1 auf viewCount(en-US). title, andere Locales und alles Übrige bleiben unverändert
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "viewCount": { "en-US": { "$+": [ "{ /payload/fields/viewCount }", 1 ] } } } }ResourceDelete
Löscht das Ziel. Es können nur die Status Draft und Archived gelöscht werden. Bei Published oder Changed wird die Löschung abgelehnt, daher müssen Sie zuerst ResourceUnpublish ausführen. Bei einem Media wird die Löschung abgelehnt, solange die Datei verarbeitet wird. Es erfolgt kein Auto-Unpublish (wie bei CMA/ACMA).
| Feld | Beschreibung |
|---|---|
resource | "Content" oder "Media" |
target | Das Ziel ({ sys: { id } }, erforderlich) |
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }ResourcePublish, ResourceUnpublish, ResourceArchive, ResourceUnarchive
Steuert unabhängig den Veröffentlichungs- und Archivierungsstatus des Ziels. Alle vier haben dieselben Felder. Die Status-Vorbedingung jeder Operation ist identisch mit CMA/ACMA. ResourcePublish ist von Archived aus nicht möglich und erfordert eine abgeschlossene Dateiverarbeitung. ResourceUnpublish ist nur von Published und Changed aus möglich, ResourceArchive nur von Draft aus und ResourceUnarchive nur von Archived aus.
| Feld | Beschreibung |
|---|---|
resource | "Content" oder "Media" |
target | Das Ziel ({ sys: { id } }, erforderlich) |
version | (Optional) Wertausdruck (Int). Optimistisches Sperren. Wenn angegeben, wird die Operation nur dann ausgeführt, wenn sie mit der aktuellen sys.version übereinstimmt |
{ "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 }" } } }Ressourcen lesen
ResourceRead und ResourceFind lesen eine Ressource und binden sie als Wert, und ResourceCount zählt nur die Anzahl. Alle drei ändern den Status nicht (kein propagateEvents). Auch bei ResourceForEach ist die Abfrage selbst ein Lesevorgang, doch wenn onEach ein ressourcenschreibendes Statement enthält, wird dieser Schreibvorgang für jedes Element ausgeführt und ändert damit den Status.
Alle vier Statements (ResourceRead, ResourceFind, ResourceForEach, ResourceCount) bestimmen mit from (Standard Current), aus welchem gespeicherten Stand gelesen wird. Current ist der neueste Entwurf, den das Content Studio sieht (der Wert, den CMA/ACMA lesen); Published ist der veröffentlichte Snapshot (der Wert zum Zeitpunkt der letzten Veröffentlichung, den CDA/ACDA ausliefern). ServiceUser wird nicht veröffentlicht und nimmt daher nur Current an (siehe Mitgliederverzeichnis lesen).
ResourceFind, ResourceForEach und ResourceCount können darüber hinaus mit advanced (Standard true) die Erweiterte Suche (Advanced Search) ein- und ausschalten. Ohne Angabe ist sie eingeschaltet. Sie ist nur für Content, daher wird sie bei Media- und ServiceUser-Lesevorgängen ignoriert. Ist sie eingeschaltet, lassen sich in where die Operatoren regex, near und within sowie die Volltextsuche für Text verwenden (bei einem LongText-Feld mit aktivierter Volltextsuche findet eq auch Einträge, die den Wert enthalten, per Teil- und Ähnlichkeitstreffer), und order kann nach fields.* sortieren. Ist sie ausgeschaltet, werden diese drei Operatoren abgelehnt, eq auf Text ist exakte Übereinstimmung, und prefix sowie die Vergleichs- und Listenoperatoren funktionieren unabhängig von der erweiterten Suche. Ein soeben erstelltes oder geändertes Element benötigt einen kurzen Moment (etwa 1 Sekunde), bis es in der erweiterten Suche erscheint, und wird daher von einer unmittelbar folgenden Abfrage der erweiterten Suche möglicherweise noch nicht gefunden. Da sie standardmäßig eingeschaltet ist, betrifft diese Verzögerung jede Abfrage, sofern Sie advanced nicht auf false setzen. Um ein soeben geschriebenes Element sofort zu lesen, verwenden Sie ResourceRead per id (aus dem primären Speicher, ohne diese Verzögerung) oder rufen Sie es über die sys.id ab, die der Schreibvorgang zurückgegeben hat.
Das createdBy: ":self" in where bedeutet „nur das, was der aktuelle Aufrufer erstellt hat". Allerdings lässt es sich in einem Script nicht verwenden, das anonyme Aufrufe erlaubt (anonymousCallEnabled). Dort würde :self nicht zum Aufrufer, sondern zum Autor aufgelöst und damit stillschweigend die Ressourcen des Autors öffnen; eine solche Definition wird deshalb beim Speichern abgelehnt (siehe Anonymer Aufruf).
In where und order schreiben Sie Content-Felder als fields.<field> (der bloße Feldname allein wird nicht erkannt). Auf fields.<field> wird automatisch die Standard-Locale des Space angewendet, daher fügen Sie keine Locale direkt an. fields.status und fields.slug in den Beispielen unten sind genau solche Abfragen über die Standard-Locale. Nur wenn Sie ausschließlich eine bestimmte (nicht standardmäßige) Locale abfragen möchten, geben Sie sie mit fields.<field>.<locale> (z. B. fields.title.ko-KR) explizit an. sys.* (z. B. sys.createdAt) und createdBy (:self) schreiben Sie ohne fields. unverändert. Die ausführlichen Regeln finden Sie unter Locale bei where und order in den Wertausdrücken.
Mitgliederverzeichnis lesen (ServiceUser)
ResourceRead, ResourceFind und ResourceForEach nehmen in resource den Wert "ServiceUser" an und lesen damit das Mitgliederverzeichnis dieses Space (ResourceCount nimmt ihn nicht an, siehe ResourceCount unten). Verwenden Sie es, um zu prüfen, wem eine Bestellung gehört, oder um ein Mitglied über die E-Mail-Adresse zu finden und dessen sys.id an das nächste Statement zu übergeben. Die folgenden Regeln gelten für alle drei Statements gemeinsam.
- Es ist nur Lesen möglich.
ResourceCreate,ResourceUpdate,ResourcePatchundResourceDeletesowie die Statements für Veröffentlichung und Archivierung nehmen"ServiceUser"nicht an, und eine solche Definition wird beim Speichern abgelehnt. Das lässt sich nicht durch zusätzliche Berechtigungen öffnen: In einem Script gibt es überhaupt keinen Weg, ein Mitglied zu ändern, daher wird es nicht als Berechtigungsfehler, sondern als falsch geschriebenes Statement abgelehnt. - Gespeichert wird nur, wenn der Autor die Berechtigung für das Mitgliederverzeichnis hat. Anders als bei Content und Media wird nicht über die Berechtigungs-Map geprüft, sondern danach, ob die
settingsder SpaceRole des AutorsSETTING_SERVICE_LOGIN(oderSETTING_ALL) enthalten. Denn das Mitgliederverzeichnis ist auch auf allen anderen Wegen eine Ressource, die von den Space-Einstellungen verwaltet wird. Fehlt sie, wird das Speichern abgelehnt (siehe Sicherheitsmodell). fromnimmt nurCurrentan. Mitglieder sind keine veröffentlichte Ressource, daher schlägt die Ausführung fehl, wenn SiePublishedübergeben.contentTypeundadvancedwerden ignoriert. Das Mitgliederverzeichnis ist nicht nach Content Type getrennt (für den gesamten Space gilt ein einziger Satz), und die erweiterte Suche ist nur für Content.- Das
sys.emailinwherenimmt nur Operatoren der exakten Übereinstimmung an (eq,ne,in,nin). Die Adresse eines Mitglieds wird verschlüsselt gespeichert, daher haben ein Vergleich der Reihenfolge oderprefixkeinen Sinn. Bei einem anderen Operator liefert es nicht stillschweigend 0 Treffer, sondern die Ausführung schlägt fehl. - Das Ergebnis ist die ServiceUser-Ressource selbst. Referenzieren Sie sie als
{ /<name>/sys/id }oder{ /<name>/nickname }. Die Struktur wird in der ServiceUser-Referenz behandelt. Wenn Sie dem gefundenen Mitglied eine E-Mail senden, holen Sie die Adresse nicht heraus, sondern übergeben Sie dessensys.idan dastoServiceUservonEmailSend(die Engine löst die Adresse unmittelbar vor dem Senden auf, sodass die Adresse des Mitglieds nicht in den Variablenraum des Script gelangt).
// ein Mitglied über die E-Mail-Adresse finden. Gibt es keines, null
{ "type": "ResourceFind", "resource": "ServiceUser",
"where": { "sys.email": { "eq": "{ /payload/fields/email }" } }, "name": "member" }ResourceRead
Liest ein einzelnes Element per id (get-by-id). Das Ergebnis bindet die gesamte Ressource an den Namen.
| Feld | Beschreibung |
|---|---|
resource | "Content", "Media" oder "ServiceUser" |
target | Das Ziel ({ sys: { id } }). Die id ist ein Wertausdruck |
from | (Optional) Current (Standard, neuester Entwurf) oder Published (veröffentlichter Snapshot). Bei ServiceUser nur Current |
- Ergebnis: Gebunden wird die Ressource selbst. Haben Sie diesem Statement einen
namegegeben, referenzieren Sie sie direkt über{ /<name>/sys/id }und{ /<name>/fields/<field>/<locale> }(beim"name": "order"des Beispiels unten also{ /order/sys/id }). Es ist keine Liste, also ist kein Array-Index im Spiel. - Wenn das Ziel nicht existiert, kommt es zu einem Fehler. Mit
Trylässt sich das behandeln.
{ "type": "ResourceRead", "resource": "Content",
"target": { "sys": { "id": "{ /payload/fields/orderId }" } }, "name": "order" }ResourceFind
Liest das erste passende Einzelelement per Filter. Wenn es keines gibt, ist es null. Verwenden Sie es, um einen Datensatz über einen eindeutigen Geschäftsschlüssel (slug, email, sku) zu finden.
| Feld | Beschreibung |
|---|---|
resource | "Content", "Media" oder "ServiceUser" |
contentType | Der Content Type, in dem gesucht wird ({ sys: { id } }). Bei Content erforderlich. Bei Media und ServiceUser ignoriert |
where | Der Filter ({ "<field>": { "<op>": <Wert> } }). Als Operatoren gelten die aus der Operatorliste (regex/near/within erfordern advanced). createdBy: ":self" wird unterstützt. Beim sys.email von ServiceUser nur eq/ne/in/nin (Mitgliederverzeichnis lesen) |
order | Die Sortierung, die bei mehreren Treffern den „ersten" bestimmt (z. B. "-sys.createdAt") |
from | (Optional) Current (Standard, neuester Entwurf) oder Published (veröffentlichter Snapshot). Bei ServiceUser nur Current |
advanced | (Optional) Über die erweiterte Suche (Advanced Search) ausführen. Nur Content (Media und ServiceUser werden ignoriert). Standard true. Siehe Hinweis Ressourcen lesen oben. |
- Ergebnis: Bindet die erste passende Ressource an den
namedieses Statements. Referenzieren Sie sie direkt als{ /<name>/fields/<field>/<locale> }. Da sienullist, wenn keine existiert, verzweigen Sie über die Existenz mit{ "==": [ "{ /<name> }", null ] }(das typische find-then-upsert-Muster).
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }, "name": "found" }ResourceForEach
Durchläuft die zum Filter passenden Ressourcen intern und führt für jedes Element onEach aus. Es ist ein Statement, das keine als Wert nutzbare Collection erzeugt, sondern an jedem Element eine Operation durchführt. Verwenden Sie es für wiederkehrende Arbeiten wie das Massenveröffentlichen von Entwürfen, das Massenändern von passendem Content oder das Versenden bzw. Synchronisieren jedes Elements nach außen. Um nur einen einzelnen Datensatz zu lesen, verwenden Sie ResourceRead (id) oder ResourceFind (Filter).
| Feld | Beschreibung |
|---|---|
resource | "Content", "Media" oder "ServiceUser" (erforderlich) |
contentType | Der Content Type, über den iteriert wird ({ sys: { id } }). Bei Content erforderlich. Bei Media und ServiceUser ignoriert |
where | Der Filter ({ "<field>": { "<op>": <Wert> } }). Die Bedeutung ist dieselbe wie beim where von ResourceFind (auch die Einschränkung für das sys.email von ServiceUser ist identisch). Als Operatoren gelten die aus der Operatorliste (regex/near/within erfordern advanced). createdBy: ":self" wird unterstützt |
order | Die Sortierung (z. B. "sys.createdAt,sys.id"). Fehlt sie, gilt die Standardreihenfolge der Plattform |
from | Current (Standard, neuester Entwurf) oder Published (veröffentlichter Snapshot). Bei ServiceUser nur Current |
advanced | Iteration über die erweiterte Suche (Advanced Search). Nur Content (Media und ServiceUser werden ignoriert). Standard true. Siehe Hinweis Ressourcen lesen oben |
limit | (Optional, ab 1) Obergrenze für die Gesamtzahl der verarbeiteten Elemente (keine Seitengröße). Fehlt sie, wird bis zur Plattformobergrenze (10.000 Elemente) iteriert |
name | (Optional) Der Name, an den das aktuelle Element gebunden wird. Wird bei jeder Iteration neu gebunden und in onEach als { /<name> } referenziert (dieselbe Lebensdauer wie name bei Loop; auch nach dem Ende der Iteration bleibt das letzte Element gebunden). Weglassen, wenn das Element nicht referenziert wird |
onEach | Das Array von Kind-Statements, das für jedes Element ausgeführt wird (erforderlich) |
- Bindet keine Collection (es ist
foreach, keinmap). Es gibt weder{ items, next }noch einen cursor. Man erhält das Iterationsergebnis nicht als Wert zurück, sondern führt pro ElementonEachaus. Wenn Sie eine Liste benötigen, sammeln Sie sie selbst mitSetVar. Wenn Sie nur die Anzahl benötigen, verwenden SieResourceCount. - Auch ohne
limitist es keine Endlositeration. Ohnelimitläuft es bis zur Plattformobergrenze (10.000 Elemente), und wenn es diese Grenze erreicht, während noch Treffer übrig sind, schlägt es fehl (um nicht als Erfolg zu melden, obwohl unbearbeitete Elemente zurückbleiben). Umgekehrt ist das Erreichen eines deklariertenlimitein beabsichtigter Halt und daher eine normale Beendigung. Einlimitüber der Obergrenze wird beim Speichern abgelehnt. - Es gibt keinen cursor. Läuft es vollständig durch, ist es ein Erfolg; wird es unterwegs abgebrochen (Wanduhr- oder Kontingentüberschreitung, ein unbehandelter Fehler in
onEach), ist es ein Fehlschlag, und der Fehler benennt, bei welchem Element und warum es fehlschlug. Ein Wiederaufsetzen drückt der Autor über seine eigenen Daten aus (setzt manwhereauf „unbearbeitet" und markiert am Ende vononEachdie Fertigstellung, wird bei einer erneuten Ausführung ab den verbliebenen fortgesetzt). - Im Zeitbudget wird es als Multiplikation gerechnet. Die Zeit, die dieses Statement deklariert, ist die von
onEachdeklarierte Zeit multipliziert mit der Anzahl der verarbeiteten Elemente (limit, ohne Angabe 10.000) (siehe Zeitbudget). Da es ein zusammengesetztes Statement mit eigenen Kindern ist, zählt es selbst nicht auf das Leaf-Budget für externe Aufrufe; stattdessen werden die externen Aufruf-Statements inonEachauf das Budget angerechnet. onEachkann wie jedes andere Statement externe Aufrufe (Http,EmailSend) oder einen Media-Datei-Ingest enthalten (wie derbodybeiLoop). Der Daseinszweck dieses Statements ist es, das Ergebnis einer Ressourcenabfrage einmal pro Element zu verarbeiten.
// alle Beiträge im Status draft finden und jeden veröffentlichen
{ "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
Zählt nur die Anzahl der zum Filter passenden Elemente. Da dabei keine Elemente gelesen werden, verwenden Sie es, wenn Sie keine Liste, sondern eine Anzahl brauchen. Es ist die Stelle, an der Sie den verbleibenden Bestand prüfen, feststellen, ob derselbe Wert bereits vorhanden ist, oder kontrollieren, ob eine Grenze überschritten wurde.
| Feld | Beschreibung |
|---|---|
resource | "Content" oder "ContentType" (erforderlich). Media und ServiceUser lassen sich nicht zählen; steht einer dieser Werte dort, wird das Speichern abgelehnt |
contentType | Der Content Type, in dem gezählt wird ({ sys: { id } }). Bei Content erforderlich. Beim Zählen von Content Type wird es ignoriert (für den gesamten Space gilt ein einziger Satz) |
where | Der Filter. Die Bedeutung ist dieselbe wie beim where von ResourceFind. Gezählt werden alle passenden Elemente |
from | (Optional) Current (Standard, neuester Entwurf) oder Published (veröffentlichter Snapshot) |
advanced | (Optional) Über die erweiterte Suche (Advanced Search) ausführen. Nur Content (beim Zählen von Content Type ignoriert). Standard true. Siehe Hinweis Ressourcen lesen oben |
name | (Optional) Der Name, an den die Anzahl gebunden wird |
- Ergebnis: Bindet die Anzahl der Treffer an den
name. Referenzieren Sie sie als{ /<name> }und verwenden Sie sie für Vergleiche und Verzweigungen. - Die Elemente selbst werden nicht zurückgegeben. Brauchen Sie die Elemente, verwenden Sie
ResourceFind(erstes passendes Einzelelement) oderResourceForEach(Ausführung pro Element). - Zählen Sie nicht, indem Sie für eine Anzahl mit
ResourceForEachiterieren. Eine Iteration veranschlagt das Zeitbudget multipliziert mit der Anzahl der Elemente (Zeitbudget), und sie schlägt fehl, wenn sie die Plattformobergrenze erreicht, während noch Treffer übrig sind. Wenn Sie nur zählen müssen, erledigt dieses Statement das in einem Zug. orderundlimitgibt es nicht. Zum Zählen wird keine Reihenfolge benötigt, und gezählt wird alles, was passt.
// zählt, wie viele Kommentare an diesem Beitrag hängen
{ "type": "ResourceCount", "resource": "Content", "contentType": { "sys": { "id": "ct_comment" } },
"where": { "fields.postId": { "eq": "{ /payload/sys/id }" } }, "name": "commentCount" }Extern
Http
Ruft einen externen HTTP-Endpunkt auf. Da es ein externer Aufruf ist, zählt es auf das tarifabhängige Limit für externe Aufrufe, und im Zeitbudget wird es als timeoutMs (ohne Angabe 30 Sekunden) × (1 + retry) gerechnet.
| Feld | Beschreibung |
|---|---|
method | "GET", "POST", "PUT", "PATCH", "DELETE" |
url | Die Ziel-URL (Wertausdruck; { /ptr } kann eingefügt werden) |
headers | [{ "key", "value", "secret"? }]. value ist ein Wertausdruck. Ein secret:true-Header wird als CMA(Administrator)-exklusiv behandelt: Er wird Endnutzern nicht offengelegt und erst unmittelbar vor dem Senden entschlüsselt. Setzen Sie hier Content-Type, wird body in dieses Format serialisiert (unten) |
body | Der Anfrage-Body (Wertausdruck oder JSON). In welchem Format er übertragen wird, bestimmt der Content-Type-Header |
timeoutMs | Das Timeout für diesen Aufruf (ms) |
retry | Anzahl der Wiederholungen, wenn der Antwort-Status 400 oder höher ist. Standard 0; die Obergrenze ist 2 |
ignoreStatusCode | Ob dieser Aufruf als Fehlschlag behandelt wird, wenn der finale Status (nach den Wiederholungen) 400 oder höher ist. Bei false (Standard) wird er als Fehlschlag behandelt und wird zu einem Try/catch-Ziel. Bei true wird er nicht als Fehlschlag behandelt, und { status, body } wird unverändert gebunden (der Aufrufer verzweigt selbst anhand von status) |
responseType | In welcher Form der Antwort-Body übergeben wird. "Json" (Standard) parst ihn zu einem Objekt oder Array, "Text" übergibt ihn als String |
- Ergebnis:
{ status, body }. Haben Sie diesem Statement einennamegegeben, dann{ /<name>/status }und{ /<name>/body/... }.responseTypebestimmt die Form vonbody. responseTypegilt nur für eine Erfolgsantwort. Der Body einer Antwort mit Status 400 oder höher wird unabhängig vom deklarierten Wert zur Diagnose gebunden (der geparste Wert, wenn es JSON ist, sonst ein String).- Ist
"Json"deklariert und der Body ist kein JSON, schlägt dieser Aufruf fehl (einTry/catch-Ziel). Bei einer API, die kein JSON liefert, empfangen Sie den Body mit"Text"und parsen ihn mitParseJson, wenn Sie ihn als Wert brauchen. "Text"wird mit dem Charset desContent-Typeder Antwort dekodiert und ohne Charset als UTF-8 behandelt. Ist der Body leer, istbodyin beiden Fällennull.- Maximale Antwortgröße: Der Antwort-Body ist maximal 10MiB groß. Wird dies überschritten, schlägt dieser Aufruf mit einer Ausnahme fehl und kann wie jeder andere Fehlschlag zur Laufzeit mit
Try/catchbehandelt werden (dies ist ein größenbasierter Fehlschlag und wird daher nicht durchignoreStatusCodeunterdrückt).
{ "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" }In welchem Format der body übertragen wird
Der in headers gesetzte Content-Type bestimmt das Serialisierungsformat von body. Der Vergleich ignoriert Groß- und Kleinschreibung sowie Parameter wie ;charset=… und betrachtet nur den vorderen Teil. Fehlt der Header oder ist sein Wert leer, wird application/json gesendet. Dieser Header wird nur angefügt, wenn ein body vorhanden ist; ohne body geht der notierte Header unverändert hinaus. Steht derselbe Schlüssel mehrfach, wird nur der erste Wert verwendet und alles zu einem Header zusammengeführt.
Ein body, der im deklarierten Format nicht abgebildet werden kann, wird auf ein Format korrigiert, das ihn abbilden kann, und so gesendet. Der Header sagt nie etwas anderes aus als der tatsächliche Body.
Diese Kombinationen gehen mit dem deklarierten Wert hinaus.
Deklarierter Content-Type | Form von body | Gesendeter body |
|---|---|---|
application/json | Beliebig | JSON |
application/x-www-form-urlencoded | Objekt oder Array | order[id]=A-2481&order[amount]=34000 |
text/plain | Skalar | Der Wert unverändert |
Sonstige (z. B. text/xml) | Beliebig | JSON |
Diese Kombinationen werden korrigiert, weil sie im deklarierten Format nicht abgebildet werden können.
Deklarierter Content-Type | Form von body | Tatsächlich gesendeter Content-Type | Gesendeter body |
|---|---|---|---|
application/x-www-form-urlencoded | Skalar | text/plain;charset=UTF-8 | Der Wert unverändert |
text/plain | Objekt oder Array | application/json | JSON |
Diese beiden Zeilen zeigen, wie die Anfrage bei einer unpassenden Paarung hinausgeht; sie sind kein Weg, das beabsichtigte Format zu erhalten. Wird body aus einem Wertausdruck zusammengesetzt, kann er je nach Payload zum Ausführungszeitpunkt ein Skalar werden, und diese Korrektur erfolgt dann ohne Fehler. Beanstandet die Gegenseite das Format, korrigieren Sie entweder die Form von body oder den Content-Type entsprechend Ihrer Absicht.
form-urlencoded entfaltet Objekte in Klammerschlüssel und Arrays in Indizes.
body | Entfaltete Schlüssel und Werte |
|---|---|
{ "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= |
Schlüssel und Werte gehen in UTF-8 prozentcodiert hinaus. Die Tabelle oben zeigt die dekodierte Form, um die Schlüsselstruktur sichtbar zu machen. Enthält ein Wert & oder +, wird dies nicht als Paartrenner oder Leerzeichen missverstanden, sondern unverändert übertragen.
Die Notation, Verschachtelungen in Klammerschlüssel zu entfalten, ist eine weit verbreitete Konvention und keine Vorgabe des Formats selbst. Prüfen Sie, ob die Gegenseite order[id] zu einem verschachtelten Objekt zurückbildet, und bauen Sie body mit flachen Schlüsseln auf, wenn sie das nicht tut.
{ "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
Versendet über ein registriertes EmailAccount 1 E-Mail. Die entgegengenommenen Felder sind nur solche, die direkt auf SMTP/MIME abgebildet werden. Es gibt keine Template-id, keinen geplanten Versand und keine anbieterspezifischen Erweiterungen (wenn Sie eine solche Funktion benötigen, rufen Sie mit Http die API des jeweiligen E-Mail-Dienstes direkt auf). Der Absender (die Absenderadresse) wird nicht hier festgelegt, sondern stammt aus dem EmailAccount, auf das account verweist.
| Feld | Beschreibung |
|---|---|
account | Die Referenz auf das EmailAccount, über das gesendet wird ({ sys: { id } }, erforderlich). Normalerweise eine literale id. Als Wertausdruck übergeben, wird sie zum Sendezeitpunkt aufgelöst und lässt sich daher beim Speichern nicht prüfen |
to | Die Empfängeradresse (Wertausdruck). Von to und toServiceUser verwenden Sie genau eines |
toServiceUser | Gibt den Empfänger als ServiceUser-Referenz an ({ sys: { id } }; die sys.id kann ein Wertausdruck sein). Da die Engine die Adresse unmittelbar vor dem Senden auflöst, gelangt die Adresse des Mitglieds nicht in den Variablenraum des Script |
cc | Array von Cc-Empfängeradressen (Wertausdruck) |
bcc | Array von Bcc-Empfängeradressen (Wertausdruck) |
subject | Der Betreff (Wertausdruck, erforderlich) |
body | Der Rumpf (Wertausdruck, erforderlich). Wird immer als text/html gesendet, verwenden Sie daher Markup statt reinem Text (Zeilenumbrüche werden zu Leerzeichen, < wird als Tag interpretiert). Das Ergebnis eines interpolierten Wertausdrucks wird HTML-escaped |
replyTo | (Optional) Der Reply-To-Header (Wertausdruck). Kann vom Absender abweichen (z. B. von no-reply senden, aber Antworten an eine Support-Adresse) |
timeoutMs | (Optional, ab 1) Das Timeout dieses Versands (ms). Fehlt es, gilt der Plattformstandard; ein Wert über der Obergrenze wird beim Speichern abgelehnt |
- Die Summe der Empfänger beträgt höchstens 50. Sie zählt
to(1),ccundbcczusammen (im SMTP-Umschlag gibt es keine Cc/Bcc-Unterscheidung, alle gehen als Empfänger hinaus, daher als Summe gezählt). Bei Überschreitung wird beim Speichern und Ausführen abgelehnt. Um an viele Personen zu senden, senden Sie mitResourceForEach+EmailSendje Element 1 E-Mail. - Bindet kein Ergebnis. Ein Erfolg bedeutet nur „der Anbieter hat die E-Mail angenommen", es gibt also keinen Rückgabewert und daher kein
name. Es wird auch nicht wiederholt (E-Mail ist nicht idempotent; ein Wiederholen nach einem unklaren Fehlschlag würde zu einem Doppelversand führen. Daher folgt es nicht demretryvonHttp). Ein Fehlschlag wird geworfen und über dascatchvonTrybehandelt. - Es ist ein externer Aufruf. Es zählt auf das tarifabhängige Limit für externe Aufrufe, und im Zeitbudget wird es einmalig als
timeoutMs(ohne Angabe 10 Sekunden) gerechnet (da es nicht wiederholt wird, wird die Anzahl nicht wie beiHttpmultipliziert). Es lässt sich innerhalb vononEachbeiResourceForEachverwenden (die Standardform für den Mehrfachversand).
{ "type": "EmailSend", "account": { "sys": { "id": "eml_orders" } },
"to": "{ /order/fields/email/en-US }",
"subject": "Bestellung erfolgreich eingegangen (Bestellnummer { /order/sys/id })",
"body": "<p>Ihre Bestellung ist eingegangen. Sobald der Versand beginnt, informieren wir Sie erneut.</p>",
"replyTo": "support@my-shop.example" }Variablen
SetVar
Deklariert oder aktualisiert eine Script-lokale, veränderbare Variable. Referenziert als { /vars/<var> } (JsonLogic hat keine Variablendeklaration, daher wird dies als Statement bereitgestellt).
| Feld | Beschreibung |
|---|---|
var | Der Variablenname. Referenziert als { /vars/<var> } |
value | Ein Wertausdruck. Kann sich selbst referenzieren, um zu akkumulieren |
{ "type": "SetVar", "var": "total", "value": 0 }
{ "type": "SetVar", "var": "total", "value": { "$+": [ "{ /vars/total }", "{ /row/qty }" ] } } // akkumulieren
{ "type": "SetVar", "var": "ids", "value": { "$merge": [ "{ /vars/ids }", [ "{ /row/sys/id }" ] ] } } // in Array sammelnZwischenspeicher
Cache
Liest und schreibt den kurzlebigen Cache, der nur diesem Script gehört. Es ist die Stelle, an der ein Wert, den man ungern jedes Mal neu holt (etwa das Ergebnis eines externen Aufrufs), einige Sekunden lang vorgehalten und beim nächsten Aufruf wiederverwendet wird. Es ist kein externer Aufruf, zählt daher nicht auf die Anzahl externer Aufrufe pro Definition und deklariert auch keine Zeit für das Zeitbudget.
| Feld | Beschreibung |
|---|---|
action | Eines von "Set" (schreiben), "Get" (lesen) und "Delete" (entfernen) (erforderlich) |
key | Der Cache-Key (erforderlich). Kein Wertausdruck, sondern ein Literal (siehe unten). Maximal 128 Zeichen; darüber wird das Speichern abgelehnt |
value | Der zu speichernde Wert (nur Set) |
ttl | Die Zeit, die der Cache lebt (nur Set, in Sekunden). Zwischen 1 und 30; ohne Angabe 5 |
defaultValue | Der Wert, den Get bindet, wenn keine zwischengespeicherten Daten vorhanden sind (nur Get). Ohne Angabe null |
name | Der Name, der das Ergebnis aufnimmt. Bei Get ist er erforderlich (hat der gelesene Wert kein Ziel, gibt es keinen Grund zu lesen). Set bindet den gespeicherten Wert und Delete, ob eine Entfernung stattgefunden hat; bei beiden ist er optional |
- Schreiben Sie nur die Felder, die zur jeweiligen Operation gehören. Steht bei
Geteinttloder beiSeteindefaultValue, wird das Speichern abgelehnt. - Nicht vorhanden und abgelaufen werden nicht unterschieden. In beiden Fällen wird
defaultValuegebunden. Dasselbe gilt, wennnullgespeichert wurde. - Der Speicherbereich ist genau dieses eine Script. Ein anderes Script desselben Space und dieses Script sehen die Daten des jeweils anderen nicht, selbst wenn sie denselben Cache-Key verwenden. Wird dieses Script geändert oder gelöscht, verschwinden alle seine Daten.
keyist ein Literal. Ließe man die Daten über einen aus der Anfrage gekommenen Cache-Key auswählen, bestimmte der Aufrufer, was er liest, und ein Script, das je Mitglied Daten abgelegt hat, gäbe den Wert des einen Mitglieds an ein anderes heraus. Steht daher ein{ /pointer }inkey, wird er weder zu einem Wert noch buchstäblich verwendet: Das Speichern selbst wird abgelehnt.- Innerhalb einer Iteration ist es nicht erlaubt. Steht ein
Cacheim Block vonLoopoderResourceForEach, wird das Speichern abgelehnt. Denn die Anzahlobergrenze unten stellt innerhalb einer Iteration keine Beschränkung dar. Da pro Durchlauf jeweils Daten geschrieben würden, gingen die Anzahl der in der Definition stehenden Statements und die Anzahl der tatsächlich verwendeten Cache-Keys auseinander. - Pro Definition sind bis zu 5 möglich (inklusive verschachtelter, unabhängig von der Operation zusammengezählt). Bei Überschreitung wird das Speichern abgelehnt.
- Der gespeicherte Wert darf höchstens 10.240 Byte (10KiB) groß sein. Darüber schlägt dieses Statement fehl (status 422). Es ist wie jeder andere Laufzeitfehlschlag und lässt sich daher mit
Try/catchlokal behandeln.
// Den Wechselkurs 30 Sekunden lang wiederverwenden.
{ "type": "Cache", "action": "Get", "name": "cached", "key": "rates" }
// Ist ein vorgehaltener Wert vorhanden, wird er ohne externen Aufruf unverändert zurückgegeben
{ "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 }" }
// Den vorgehaltenen Wert vor dem Ablauf verwerfen
{ "type": "Cache", "action": "Delete", "key": "rates" }Wert-Parsing
ParseJson
Parst JSON-Text zu dem Wert, den er bezeichnet, und bindet ihn an einen Namen. Verwenden Sie es für einen Body, den Http mit responseType: "Text" empfangen hat, für einen JSON-String, der im Payload ankommt, oder für JSON, das als String in einem Feld liegt. Es ist kein externer Aufruf, zählt daher nicht auf das Limit für externe Aufrufe und deklariert auch keine Zeit für das Zeitbudget.
| Feld | Beschreibung |
|---|---|
name | Der Name, der den geparsten Wert aufnimmt (erforderlich). Bei anderen Statements ist er optional, hier ist er erforderlich. Das Statement tut nichts außer sein Ergebnis zu binden, eines ohne Namen hat also überhaupt keine Wirkung |
value | Der zu parsende JSON-Text (Wertausdruck, erforderlich). Zeigen Sie wie in { /resp/body } auf einen Wert aus einem früheren Schritt, oder schreiben Sie den JSON-Text wörtlich hin (ein { innerhalb des Literals wird nicht als { pointer }-Template gelesen) |
- Ergebnis: der geparste Wert selbst. Ein Objekt bleibt ein Objekt, ein Array bleibt ein Array, und ein einzelner Wert wie
42oder"a"wird ebenso geparst. Danach adressieren Sie das Innere mit{ /<name>/... }. - Ein Wert, der bereits geparst ist, wird unverändert gebunden. Löst
valuezu etwas auf, das kein String ist, gibt es keinen Text zu parsen, und dieser Wert wird so gebunden, wie er ist. - Ein
{ /pointer }innerhalb des geparsten Textes wird nicht erneut aufgelöst. Selbst wenn ein von außen empfangener String einen Ausdruck wie{ /payload/... }enthält, wird er nicht ersetzt und bleibt Text. nulldeckt zwei verschiedene Fälle ab. Ist der zu parsende Text das einzelne Wortnull, ist das normal und das Ergebnis istnull. Ist die Stelle, auf dievaluezeigt, dagegen leer, gibt es also überhaupt keinen Wert, dann ist nichts zu parsen und das Statement schlägt fehl.- Fehlschlag: wenn
valuezu keinem Wert oder nur zu Leerzeichen auflöst, und wenn der Text kein JSON ist. Behandeln Sie ihn wie jeden anderen Laufzeitfehlschlag mitTry/catch; die Fehlermeldung führt den Text mit, den zu parsen versucht wurde. - Es zählt als ein Statement gegen die Statement-Anzahl pro Definition, hat aber nichts mit dem Limit für externe Aufrufe oder der
SetVar-Obergrenze zu tun.
// 1) Eine API, die kein JSON liefert: als Text empfangen, dann parsen
{ "type": "Http", "method": "GET", "url": "https://api.partner.example/v1/quote",
"responseType": "Text", "name": "resp" },
{ "type": "ParseJson", "name": "quote", "value": "{ /resp/body }" },
// 2) Einen JSON-String parsen, der im Payload angekommen ist
{ "type": "ParseJson", "name": "spec", "value": "{ /payload/fields/specJson }" }Signaturprüfung und Textverarbeitung
Diese Statements prüfen die Signatur, die ein Zahlungsdienstleister per Webhook gesendet hat, und entpacken den String, in dem diese Signatur verpackt ankommt. Alle drei sind keine externen Aufrufe, sondern Berechnungen; sie zählen daher nicht auf das Limit für externe Aufrufe und deklarieren auch keine Zeit für das Zeitbudget, und da sie keine Datenstelle haben, sind sie von der $-Präfixregel unabhängig. Ein vollständiges Beispiel, das alle drei kombiniert, finden Sie unter Webhook-Signaturprüfung im Cookbook.
Bei allen drei Statements ist die Länge des aufgelösten Werts begrenzt. Gemeint ist nicht die Länge des Ausdrucks, sondern die Länge des Werts, auf den der Ausdruck zeigt (die sechzehn Zeichen { /rawPayload } zeigen auf mehrere Dutzend KB), und bei Überschreitung schlägt die Ausführung fehl, was sich mit Try behandeln lässt. Die Zahlen sind unter Obergrenzen für Wertlängen zusammengefasst.
Signature
Prüft, ob der empfangene Signaturcode mit dem Code übereinstimmt, der mit secret erzeugt wird, und bindet diese Antwort als Boolean-Wert. Die Signatur, die ein Zahlungsdienstleister (PG, MoR) per Webhook sendet, prüfen Sie mit diesem Statement.
| Feld | Beschreibung |
|---|---|
name | Der Name, der das Prüfergebnis aufnimmt (erforderlich). { /<name> } ist true oder false. Zu prüfen und das Ergebnis dann nicht zu verwenden ist dasselbe wie nicht zu prüfen, daher lässt er sich nicht weglassen |
algorithm | Der Hash, mit dem der Code erzeugt wird (erforderlich). SHA1, SHA256, SHA384, SHA512 |
secret | Der mit der Gegenseite geteilte geheime Schlüssel (Wertausdruck, erforderlich) |
secretEncoding | In welcher Notation secret geschrieben ist. Utf8 (Standard, Textschlüssel), Hex, Base64. Lassen Sie einen als hex oder base64 ausgegebenen Schlüssel als Text stehen, wird er zu einem anderen Schlüssel: Es entsteht ein plausibler Code, der jedoch nie passt |
value | Die Nachricht, für die der Code berechnet wird (Wertausdruck, erforderlich). Sie muss mit den von der Gegenseite signierten Bytes zeichengenau übereinstimmen und ist daher meist { /rawPayload } oder dies mit dem Zeitstempel davor, den der Anbieter im Header mitgesendet hat |
expected | Der vom Aufrufer gesendete Code (Wertausdruck, erforderlich). Beispiel: { /headers/x-signature } |
- Ergebnis: ein
Boolean. Danach verwenden Sie{ /<name> }unverändert als Bedingung einesIf. - Schreiben Sie für
valuenicht das geparste/payload, sondern/rawPayload. Macht man aus dem geparsten Payload wieder einen String, werden Leerzeichen, Zahlennotation und Escapes normalisiert, und man kommt nicht zu den Bytes zurück, die die Gegenseite signiert hat (Kontextwurzeln). - Es gibt kein Feld, das die Ausgabenotation angibt.
algorithmlegt die Byte-Länge des Codes fest, und hex und base64 gleicher Länge haben keine überschneidenden Stringlängen; daher stellt die Engine die Bytes wieder her, auch ohne dass die Gegenseite mitteilt, in welcher Form sie gesendet hat. Groß- und Kleinschreibung bei hex sowie base64 und base64url (einschließlich mit oder ohne Padding) werden aus demselben Grund nicht unterschieden. - Ob es ein Fehlschlag oder ein
falseist, entscheidet sich daran, wer den Wert liefert.- Fehlt
expectedoder passt der Code nicht, ist das Ergebnis lediglichfalseund kein Fehlschlag. Denn würde man einen fehlenden Header und einen nicht passenden Code getrennt melden, verriete man der sendenden Seite, welches von beidem falsch war. - Ist
valueleer, wird mit einer leeren Nachricht gerechnet. Auch ein leerer Body ist Gegenstand einer Signatur. - Fehlt
secretoder entspricht es nicht der insecretEncodingdeklarierten Notation, ist es ein Fehlschlag. Von den drei Fällen ist dies die einzige Eingabe des Autors selbst. In der Fehlermeldung werdensecretundvaluenicht mitgeführt.
- Fehlt
- Die Obergrenze für
valuebeträgt 65.536 Zeichen (bezogen auf den aufgelösten Wert). Der Wert ist an der Größe der Webhook-Bodies ausgerichtet, die reale Anbieter senden. - Der Vergleich prüft die Gleichheit der Werte in konstanter Zeit (constant-time). Wie viele Bytes am Anfang gepasst haben, sickert nicht über die Antwortzeit nach außen.
secretwird nicht verschlüsselt gespeichert. Anders alssecret: truein einemHttp-Header (verschlüsselt gespeichert, unmittelbar vor dem Senden entschlüsselt) bleibt es so stehen, wie es in der Definition geschrieben ist, daher sehen Rollen, die dieses Script lesen können, den Wert. Mitglieder (ServiceUser) können die Definition eines Script nicht lesen (Autorenschaft und Abfrage sind CMA-exklusiv).
// ein Anbieter, der den gesamten Body signiert
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
"secret": "whsec_9f2c1b7ae4", "value": "{ /rawPayload }",
"expected": "{ /headers/x-webhook-signature }" }
// ein Anbieter, der den Schlüssel als base64 ausgibt
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
"secret": "aGVsbG8td2VlZ2xvbw==", "secretEncoding": "Base64",
"value": "{ /rawPayload }", "expected": "{ /headers/webhook-signature }" }Hash
Bildet den Digest von value und bindet ihn als String in der Notation, die encoding festlegt. Verwenden Sie es, um kein HMAC nachzubilden, sondern ein Signaturschema, das „einige Felder und den geheimen Schlüssel aneinanderhängt und daraus SHA256 berechnet".
| Feld | Beschreibung |
|---|---|
name | Der Name, der den Digest aufnimmt (erforderlich) |
algorithm | MD5, SHA1, SHA256, SHA384, SHA512 (erforderlich). MD5 dient dazu, alte Schemata nachzubilden, die es verlangen, und ist kein Wert, den man für eine neu entworfene Signatur wählt |
value | Die zu hashende Nachricht (Wertausdruck, erforderlich) |
encoding | Die Notation des Ergebnisses. Hex (Standard), HexUpper, Base64, Base64Url |
- Es gibt kein
secret-Feld. Da der Schlüssel je Schema vorne, hinten oder in der Mitte steht, drückt es alle Positionen aus, wenn man den Schlüssel direkt invalueschreibt. - Ergebnis: ein String. Für den Vergleich mit dem Code, den die Gegenseite gesendet hat, schreiben Sie
{ "==": [ "{ /<name> }", "{ /headers/... }" ] }. Dieser Vergleich ist anders als der constant-time-Vergleich vonSignatureein normaler Gleichheitsvergleich. - Löst
valuezu keinem Wert oder nur zu Leerzeichen auf, ist es ein Fehlschlag (denn es ist ein Ausdruck des Autors selbst). - Die Obergrenze für
valuebeträgt 128 Zeichen. Da es die Stelle für einige aneinandergehängte Felder ist, ist sie viel enger als beiSignature. Müssen Sie über den gesamten Webhook-Body rechnen, verwenden SieSignature.
// SHA256(Bestellnummer + Betrag + merchantKey) als Hex in Großbuchstaben
{ "type": "Hash", "name": "expectedSign", "algorithm": "SHA256", "encoding": "HexUpper",
"value": "{ /payload/orderId }{ /payload/amount }9f2c1b7ae4" }Regex
Wendet pattern auf value an und bindet das, was mode verlangt. Wertausdrücke haben kein Mittel, einen String zu zerteilen (es gibt nur cat zum Aneinanderhängen und in für die Prüfung auf Enthaltensein), daher verwenden Sie dieses Statement, wenn wie bei t=…,v1=… mehrere Werte in einem Header verpackt ankommen.
| Feld | Beschreibung |
|---|---|
name | Der Name, der das Ergebnis aufnimmt (erforderlich). Bei Capture adressieren Sie ein Element als { /<name>/1 } |
mode | "Match" bindet den Trefferstatus als Boolean, "Capture" bindet den ersten Treffer als Array (erforderlich) |
pattern | Der reguläre Ausdruck (erforderlich). Kein Wertausdruck, sondern ein Literal (siehe unten). Flags schreiben Sie wie (?i) in das Pattern. Maximal 128 Zeichen; darüber wird das Speichern abgelehnt |
value | Der Text, auf den das Pattern angewendet wird (Wertausdruck, erforderlich). Überschreitet der aufgelöste Wert 10.240 Zeichen (10KiB), schlägt die Ausführung fehl |
- Ergebnis:
Matchist einBoolean,Captureist ein Array odernull. Im Array ist Index0der gesamte Treffer, ab1folgen die Capture-Gruppen, und eine Gruppe, die nicht beteiligt war, istnull(kein leerer String, denn der wäre ein Treffer). Tritt das Pattern nicht auf, istCapturekein leeres Array, sondernnull. - Beide Modi fragen, „ob das Pattern irgendwo auftritt". Soll der gesamte Text dem Pattern entsprechen, verankern Sie es mit
^…$. Die Frage ist bewusst gleich gehalten, damit zwei Statements, die erst mitMatchprüfen und dann mitCaptureherausholen, nicht unterschiedliche Antworten geben. patternist eines der beiden Felder dieser Engine, die kein Wertausdruck sind (das andere ist daskeyvonCache). Würde ein aus der Anfrage gekommenes Pattern unverändert ausgeführt, wählte der Aufrufer den auszuführenden Ausdruck, und das Backtracking regulärer Ausdrücke machte daraus ein Mittel für Denial of Service. Daher wird auch ein{ /pointer }innerhalb des Patterns nicht zu einem Wert, sondern buchstäblich Teil des Patterns.- Das Pattern wird beim Start der Ausführung einmal für die gesamte Definition kompiliert. Auch innerhalb von
LoopoderResourceForEachwird es nicht bei jeder Iteration erneut kompiliert, und ein unbrauchbares Pattern schlägt fehl, bevor das erste Statement irgendetwas tut (mitTrybehandelbar).
// "t=1492774577,v1=<64 Zeichen hex>" entpacken zu { /sig/1 } = Zeitstempel, { /sig/2 } = Code
{ "type": "Regex", "name": "sig", "mode": "Capture",
"pattern": "^t=(\\d+),v1=([0-9a-f]{64})$", "value": "{ /headers/x-provider-signature }" }
// nur das Format prüfen
{ "type": "Regex", "name": "isOrderId", "mode": "Match",
"pattern": "^ORD-\\d{8}-\\d{4}$", "value": "{ /payload/orderId }" }Ablaufsteuerung
If
Eine bedingte Verzweigung. condition ist JsonLogic, und ob etwas wahr oder falsch ist, folgt den Regeln unter Wahrheitswert.
| Feld | Beschreibung |
|---|---|
condition | JsonLogic (als Boolean ausgewertet) |
then | Das Statement-Array, das bei true ausgeführt wird |
else | (Optional) Das Statement-Array, das bei false ausgeführt wird |
{ "type": "If",
"condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" } } ],
"else": [ /* ... */ ] }Loop
Iteration. Wählen Sie einen Modus: over (foreach), while (Bedingung) oder for (Zählung). In jedem Modus erzwingt die Engine eine Obergrenze für die Iterationen (verhindert Endlosschleifen). Die Obergrenze deklarieren Sie mit maxIterations; ohne Angabe gilt die Plattformobergrenze. Im body lassen sich auch externe Aufrufe (Http, EmailSend) und ein Media-Datei-Ingest unterbringen, und die externen Aufruf-Statements werden zur Ausführung bei jeder Iteration tatsächlich aufgerufen. Das Limit für die maximale Anzahl externer Aufrufe pro Definition gilt weiterhin.
Im Zeitbudget wird es als Multiplikation gerechnet. Die Zeit, die dieses Statement deklariert, ist die vom body deklarierte Zeit multipliziert mit maxIterations (ohne Angabe 10.000) (siehe Zeitbudget). Enthält der body keinen externen Aufruf, ist die deklarierte Zeit 0, sodass das Grundbudget von 30 Sekunden die tatsächliche Grenze ist.
| Feld | Beschreibung |
|---|---|
over | foreach: ein Wertausdruck, der zu einem Array aufgelöst wird |
while | Bedingung: JsonLogic (wiederholt, solange true) |
for | Zählung: { "from", "to", "step"? }. Von from bis to inklusive; step standardmäßig 1 |
maxIterations | Maximale Iterationsanzahl (optional). Ohne Angabe gilt die Plattformobergrenze 10.000; ein größerer Wert wird beim Speichern abgelehnt |
name | (Optional) Der Name, an den das aktuelle Element (foreach) oder der Index (while, for) gebunden wird ({ /<name> }) |
body | Das Statement-Array für den Schleifenkörper |
// 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
Führt die Branches gleichzeitig aus und fährt nach dem Zusammenführen fort. Referenzen zwischen Branches sind nicht möglich (bei einer Abhängigkeit ordnen Sie sie sequenziell an).
| Feld | Beschreibung |
|---|---|
branches | Statement[][]. Jedes Element ist ein Branch (ein Array von 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
Dies ist das return aus der normalen Programmierung. Es gibt das Ergebnis des Script an den Aufrufer zurück und beendet an dieser Stelle normal.
| Feld | Beschreibung |
|---|---|
value | (Optional) Der zurückzugebende Wertausdruck |
isError | Standard false. Bei true kommt value als error der Antwort zurück (andernfalls als return) |
statusCode | Der Antwort-Statuscode. Standard 200 |
- Wird
Returnnie erreicht, gibt es keinen Rückgabewert. Um ein Ergebnis zurückzugeben, geben Sievalueexplizit an. - Da es sich um eine normale Beendigung handelt, nicht um eine Ausnahme oder einen throw, ist es kein
catch-Ziel (auch innerhalb vonTrybeendet es das gesamte Script, aberfinallywird dennoch ausgeführt). - Auch ein Guard wird mit diesem Statement ausgedrückt. Setzen Sie ein
Returnin dastheneinesIf, gibt es bei einer Verletzung der Bedingung einen Wert zurück und führt die nachfolgenden Statements nicht aus. Das ist eine der mehreren Verwendungen vonReturn.
{ "type": "Return", "value": { "orderId": "{ /order/sys/id }", "status": "paid" }, "statusCode": 201 }
{ "type": "Return", "value": { "reason": "payment failed" }, "isError": true, "statusCode": 402 }Try
Ausnahmebehandlung.
| Feld | Beschreibung |
|---|---|
body | Das zu versuchende Statement-Array |
catch | (Optional) Wird ausgeführt, wenn body fehlschlägt. Legt { message } unter /error offen (welches Statement fehlgeschlagen ist, wird nicht mitgeführt) |
finally | (Optional) Wird unabhängig von Erfolg oder Fehlschlag immer ausgeführt |
- Wenn
catchden Fehler behandelt, wird das Script nicht abgebrochen. Nur ein Fehlschlag ohnecatchbricht das Script ab (einschließlich eines Kompensationsversuchs). - Was als „Fehlschlag" gilt und die Grenzen der Kompensation (compensation) werden unter Ausführungssemantik, Einschränkungen und Sicherheit behandelt.
{ "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": [ /* läuft immer */ ] }Verwandte Dokumente
- Wertausdrücke: Die Wertregeln, denen alle oben genannten Felder folgen.
- Ausführungssemantik, Einschränkungen und Sicherheit: Ausführungsreihenfolge, Fehler, statische Einschränkungen und Sicherheit.
- Cookbook: Vollständige Beispiele, die diese Statements kombinieren.
- Script-Übersicht: Die oberste Struktur und die Zeit für eine einzelne Ausführung.
