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 drei Ausnahmen: pattern bei Regex, key bei Cache und color bei PdfWatermark (siehe Regex, Cache und Dateitransformation).

Statement-Zusammenfassung

KategorietypeEinzeilige Zusammenfassung
Ressourcen schreibenResourceCreateContent/Media erstellen (optional veröffentlichen)
ResourceUpdateVollständige Ersetzung der Content/Media-Felder (nicht angegebene Felder/Locales werden gelöscht)
ResourcePatchPartielle Zusammenführung der Content/Media-Felder (nur die angegebenen Felder/Locales; ein literales null löscht)
ResourceDeleteLöschen (nur Draft/Archived; wenn Published, zuerst unpublish)
ResourcePublish / ResourceUnpublishVeröffentlichen / Zurückziehen
ResourceArchive / ResourceUnarchiveArchivieren / Wiederherstellen
Ressourcen lesenResourceReadPer id ein einzelnes Element lesen
ResourceFindPer Filter das erste passende Element (null, wenn keines)
ResourceForEachDurchläuft die zum Filter passenden Ressourcen intern und führt für jedes Element onEach aus
ResourceCountZählt nur die Anzahl der zum Filter passenden Elemente (die Elemente werden nicht gelesen)
ExternHttpExterner HTTP-Aufruf ({ status, body }). Kann eine Datei als Body senden
EmailSendVersendet über ein registriertes EmailAccount 1 E-Mail
VariablenSetVarScript-lokale Variable deklarieren/aktualisieren
ZwischenspeicherCacheDen kurzlebigen Cache, der nur diesem Script gehört, lesen, schreiben oder entfernen
Wert-ParsingParseJsonJSON-Text zu einem Wert (Objekt, Array, Skalar) parsen und binden
Signatur und TextSignaturePrüft, ob der empfangene Signaturcode mit dem Code übereinstimmt, der aus dem geheimen Schlüssel erzeugt wird (Boolean)
HashDigest ohne Schlüssel berechnen (String)
RegexRegulären Ausdruck anwenden. Trefferstatus (Boolean) oder Capture-Gruppen (Array)
AblaufsteuerungIfBedingte Verzweigung
LoopIteration (foreach / while / counted)
ParallelBranches gleichzeitig ausführen
ReturnErgebnis zurückgeben und vorzeitig beenden
TryAusnahmebehandlung (catch/finally)

Ein Statement, das Content bearbeitet und sein Ziel nicht per id angibt, muss immer den Content Type nennen. Bei ResourceFind, ResourceForEach und ResourceCount ist contentType erforderlich, sobald resource den Wert "Content" hat. Eine Content-Abfrage, die den gesamten Space durchquert, gibt es nicht. Auch ResourceCreate nennt 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, ResourcePatch und ResourceDelete sowie die Statements für Veröffentlichung und Archivierung), haben target und brauchen deshalb keinen Bereich.

Zyklische Aufrufe sind auf 3 begrenzt. Schalten Sie bei den obigen ressourcenschreibenden Statements (ResourceCreate, ResourceUpdate, ResourcePublish usw.) propagateEvents ein (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: name ist 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, secrets) ü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.id ist normalerweise ein Literal (z. B. "ct_post").
  • target.sys.id ist 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.

Die Statements, die einen ServiceUser (ein für das Produkt registriertes Mitglied) annehmen, sind die drei Lese-Statements (ResourceRead, ResourceFind, ResourceForEach) sowie ResourceUpdate und ResourcePatch. Die einzigen Felder, die diese beiden schreiben können, sind roleOverride und enableLogin. Steht er in einem anderen Schreib-Statement, wird das Speichern abgelehnt (siehe Fehler). Die Regeln werden unter Mit dem Mitgliederverzeichnis arbeiten behandelt.

Ressourcen schreiben

Jedes Schreib-Statement hat propagateEvents (Standard false) (ResourceUpdate und ResourcePatch, die einen ServiceUser schreiben, nehmen dieses Feld nicht an). 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.

FeldGilt fürBeschreibung
resourceGemeinsam"Content" oder "Media" (erforderlich)
contentTypeContentDer zu erstellende Content Type ({ sys: { id } }). Bei Content erforderlich
fieldsGemeinsamFeld-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)
localeGemeinsam(Komfort) Wenn angegeben, wird jeder Wert in fields automatisch als { <locale>: Wert } verpackt
publishGemeinsamNach dem Schreiben veröffentlichen (Anzeige über CDA/ACDA). Standard true
  • Media file: Der Wert von fields.file.{locale} ist eine Ingest-Anweisung { "source": <Wertausdruck>, "encoding": "url"|"base64", "transformations": [ … ] }. source und encoding sind erforderlich; transformations geben Sie nur an, wenn die empfangene Datei vor dem Speichern verändert werden soll (Dateitransformation). Bei einem Schreibvorgang, der eine Datei enthält, führt die Engine den Ingest durch (bei url wird heruntergeladen, bei base64 wird 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. Bei publish:true, aber ohne Datei oder mit unfertiger Verarbeitung, tritt im Veröffentlichungsschritt ein Fehler auf; bei publish:false bleibt es Draft.
  • 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" }

Dateitransformation (transformations)

Beim Verkauf eines E-Books möchten Sie dem Käufer vielleicht eine Kopie übergeben, auf deren PDF-Seiten durchgehend seine E-Mail-Adresse aufgedruckt ist. Geben Sie in der Ingest-Anweisung transformations an, lässt sich die empfangene Datei vor dem Speichern verändern. Unterstützt wird eine einzige Transformation: PdfWatermark.

Feld der Ingest-AnweisungErforderlichBeschreibung
transformationsOptionalEin Array von Transformationsobjekten. Ohne Angabe oder als leeres Array wird die empfangene Datei unverändert in das Media übernommen. Ist es kein Array, wird das Speichern des Script abgelehnt
  • Die Transformationen werden in der Reihenfolge des Arrays angewendet. Das Ergebnis einer Transformation ist die Eingabe der nächsten, und das letzte Ergebnis wird zur Datei des Media.
  • Sie können in jeder Ingest-Anweisung von ResourceCreate, ResourceUpdate und ResourcePatch angegeben werden.
  • Das Original, auf das source zeigt, bleibt unverändert. Um Text auf eine Datei aufzudrucken, die bereits im Space liegt, lesen Sie das Media mit ResourceRead, setzen { /<name>/fields/file/<locale>/url } in source ein und erstellen ein neues Media.
  • Auch die Transformation ist Teil des Ingest. Die benötigte Zeit geht vom Grundbudget von 30 Sekunden ab (Zeitbudget).

PdfWatermark: Text auf alle Seiten eines PDF aufdrucken

FeldErforderlichBeschreibung
typeErforderlich"PdfWatermark". Bei einem anderen Wert wird das Speichern des Script abgelehnt
textErforderlichDer aufzudruckende Text. Ein String, in den Sie { /pointer }-Referenzen einfügen können. JsonLogic-Operationsobjekte werden nicht angenommen. Er muss eine einzelne Zeile mit 1-64 Zeichen aus zulässigen Zeichen sein
colorOptionalDie Schriftfarbe. Geben Sie sie als #RRGGBB (sechs Hexadezimalziffern) an; ohne Angabe gilt #808080 (Grau). Angenommen wird kein Wertausdruck, sondern nur ein Literal; bei einem anderen Format wird das Speichern des Script abgelehnt
  • Felder, die nicht in der Tabelle stehen, werden ignoriert. Schriftgröße, Position und Deckkraft lassen sich nicht festlegen.
  • Der Text wird auf alle Seiten gedruckt. Die Textzeile liegt auf der Diagonale, die durch die Seitenmitte verläuft (von links unten nach rechts oben).
  • Er wird mit 30 % Deckkraft über den Inhalt gelegt. Der ursprüngliche Inhalt unter dem Text bleibt lesbar.
  • Die Schriftgröße wird für jede Seite automatisch bestimmt. Der Text wird so groß gedruckt, dass er 80 % der Diagonalenlänge einnimmt, jedoch höchstens 15 % der kürzeren Seitenkante. Kurzer Text wird groß, langer Text klein gedruckt, und er ragt nicht über die Seite hinaus.
  • Die Schriftart ist Helvetica, eine PDF-Standardschrift.
  • Ob es sich um ein PDF handelt, wird nicht am Dateinamen oder an der Endung erkannt, sondern am Inhalt.
// Teil des statements-Arrays. Ein neues Media, bei dem E-Mail-Adresse und Bestellnummer des Käufers auf das gekaufte E-Book-PDF gedruckt sind
{ "type": "ResourceRead", "resource": "Media",
  "target": { "sys": { "id": "{ /payload/fields/ebookMediaId }" } }, "name": "ebook" },
{ "type": "ResourceCreate", "resource": "Media", "locale": "en-US", "publish": false,
  "fields": {
    "title": "{ /ebook/fields/title/en-US }",
    "file": {
      "source": "{ /ebook/fields/file/en-US/url }", "encoding": "url",
      "transformations": [
        { "type": "PdfWatermark", "text": "{ /payload/fields/email } · { /payload/fields/orderId }", "color": "#B22222" }
      ]
    }
  }, "name": "stamped" }

Da es sich um eine Kopie für einen einzelnen Käufer handelt, ist publish auf false gesetzt, damit sie nicht über CDA/ACDA ausgeliefert wird. Ohne Angabe wird sie gemäß dem Standardwert true veröffentlicht.

Einschränkungen für den Text von PdfWatermark

Im text sind nur Zeichen zulässig, die die PDF-Standardschrift darstellen kann (der WinAnsi-Zeichensatz).

ArtZeichen
Druckbare ASCII-ZeichenLeerzeichen, lateinische Groß- und Kleinbuchstaben, Ziffern und grundlegende Symbole wie @ . # { }
Latin-1-ZeichenWesteuropäische Zeichen und Symbole wie é ü ñ ß · © °. Ausgenommen sind das geschützte Leerzeichen (U+00A0) und der weiche Trennstrich (U+00AD)
Zusätzliche Symbole€ ‚ ƒ „ … † ‡ ˆ ‰ Š ‹ Œ Ž ‘ ’ “ ” • ˜ ™ š › œ ž Ÿ sowie zwei Arten von Gedankenstrichen (U+2013, U+2014)

Andere Zeichen sind nicht zulässig. Dazu gehören erweiterte lateinische Zeichen, die nicht in der Tabelle stehen (etwa ł, ő, ș), nicht lateinische Schriftzeichen, Emojis sowie Zeilenumbrüche und Tabulatoren.

Beim Speichern des Script wird das text so geprüft, wie es geschrieben wurde. Bei einem text mit { /pointer } steht der aufgelöste Wert erst zur Ausführungszeit fest, daher wird der aufgelöste Wert bei der Ausführung erneut geprüft. Die Codes der Fehler, die beim Speichern auftreten, finden Sie unter Fehler.

EinschränkungLiterales texttext mit { /pointer }
Eine einzelne Zeile aus zulässigen ZeichenSpeichern des Script abgelehntBeim Speichern wird der gesamte geschriebene String (einschließlich der Pointer-Pfade) geprüft, Speichern des Script abgelehnt. Der aufgelöste Wert wird bei der Ausführung erneut geprüft, Fehlschlag mit Status 400
1-64 ZeichenSpeichern des Script abgelehntBeim Speichern nicht geprüft. Bei der Ausführung wird die Länge des aufgelösten Werts geprüft, Fehlschlag mit Status 400
  • Berechnen Sie die Länge des festen Texts mit ein. Bei "Purchased by { /payload/fields/email } · Order { /payload/fields/orderId }" umfasst der feste Text außerhalb der Referenzen 22 Zeichen, sodass für E-Mail-Adresse und Bestellnummer zusammen 42 Zeichen bleiben. Kommt ein tatsächlicher Wert an, der länger ist, wird das Script zwar gespeichert, doch diese Ausführung schlägt fehl.
  • Drucken Sie Kennungen statt Namen auf. Ein Käufername mit Zeichen, die nicht in der Tabelle stehen, schlägt bei der Ausführung fehl. Eine E-Mail-Adresse oder Bestellnummer in ASCII lässt sich aufdrucken.

Wenn eine Dateitransformation fehlschlägt

In den folgenden Fällen schlägt das betreffende Statement fehl. Wie jeder andere Laufzeitfehlschlag lässt es sich mit Try/catch behandeln. Die Transformation findet statt, bevor das Media geschrieben wird; bei einem Fehlschlag wird daher kein neues Media erstellt, und auch das Ziel von ResourceUpdate bzw. ResourcePatch bleibt unverändert.

SituationErgebnis
Die Datei aus source ist kein PDFSchlägt mit Status 422 fehl
Das PDF lässt sich nicht lesen oder ist verschlüsselt (einschließlich PDFs, die beim Öffnen kein Passwort abfragen und nur Berechtigungen einschränken)Schlägt mit Status 422 fehl
Das aufgelöste text ist leer oder besteht nur aus LeerzeichenSchlägt mit Status 400 fehl
Das aufgelöste text liegt außerhalb von 1-64 Zeichen oder enthält unzulässige ZeichenSchlägt mit Status 400 fehl

ResourceUpdate

Führt eine vollständige Ersetzung der Felder des Ziel-Content, -Media oder -ServiceUser 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.

FeldBeschreibung
resource"Content", "Media" oder "ServiceUser". Welche Felder Sie bei einem ServiceUser schreiben können und welche Einschränkungen gelten, steht unter Mit dem Mitgliederverzeichnis arbeiten
targetDas Ziel ({ sys: { id } }, erforderlich). Die id ist normalerweise { /ptr }
fieldsDer 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). Ein ServiceUser nimmt es nicht an
publishNach 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, -Media oder -ServiceUser 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.

FeldBeschreibung
resource"Content", "Media" oder "ServiceUser". Welche Felder Sie bei einem ServiceUser schreiben können und welche Einschränkungen gelten, steht unter Mit dem Mitgliederverzeichnis arbeiten
targetDas Ziel ({ sys: { id } }, erforderlich). Die id ist normalerweise { /ptr }
fieldsDie 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). Ein ServiceUser nimmt es nicht an
publishNach der Aktualisierung erneut veröffentlichen. Standard true
  • Eine bestimmte Locale oder Datei löschen: Geben Sie als Wert ein literales null an. 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 file eine 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).

FeldBeschreibung
resource"Content" oder "Media"
targetDas 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.

FeldBeschreibung
resource"Content" oder "Media"
targetDas 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 Mit dem Mitgliederverzeichnis arbeiten).

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.

Mit dem Mitgliederverzeichnis arbeiten (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 fünf Statements, die mit dem Mitgliederverzeichnis arbeiten.

  • Es lassen sich nur zwei Felder ändern. ResourceUpdate und ResourcePatch nehmen "ServiceUser" an und ändern roleOverride und enableLogin. Übergeben Sie bei ResourcePatch für roleOverride ein null, wird die zugewiesene Rolle aufgehoben. ResourceCreate und ResourceDelete sowie 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: Ein Script kann ein Mitglied nicht erstellen oder löschen, daher wird es nicht als Berechtigungsfehler, sondern als falsch geschriebenes Statement abgelehnt.
  • Ein Mitglied hat weder version noch propagateEvents. Geben Sie eines von beiden an, schlägt die Ausführung fehl. ResourceUpdate ersetzt alle schreibbaren Felder, daher ist enableLogin erforderlich, und wenn Sie roleOverride weglassen, wird die zugewiesene Rolle aufgehoben. Um nur eines von beiden zu ändern, verwenden Sie ResourcePatch; dort muss mindestens eines der beiden Felder angegeben sein.
  • 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 settings der SpaceRole des Autors SETTING_SERVICE_LOGIN (oder SETTING_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).
  • from nimmt nur Current an. Mitglieder sind keine veröffentlichte Ressource, daher schlägt die Ausführung fehl, wenn Sie Published übergeben.
  • contentType und advanced werden 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.email in where nimmt nur Operatoren der exakten Übereinstimmung an (eq, ne, in, nin). Die Adresse eines Mitglieds wird verschlüsselt gespeichert, daher haben ein Vergleich der Reihenfolge oder prefix keinen 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 dessen sys.id an das toServiceUser von EmailSend (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.

FeldBeschreibung
resource"Content", "Media" oder "ServiceUser"
targetDas 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 name gegeben, 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 Try lä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.

FeldBeschreibung
resource"Content", "Media" oder "ServiceUser"
contentTypeDer Content Type, in dem gesucht wird ({ sys: { id } }). Bei Content erforderlich. Bei Media und ServiceUser ignoriert
whereDer 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 (Mit dem Mitgliederverzeichnis arbeiten)
orderDie 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 name dieses Statements. Referenzieren Sie sie direkt als { /<name>/fields/<field>/<locale> }. Da sie null ist, 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).

FeldBeschreibung
resource"Content", "Media" oder "ServiceUser" (erforderlich)
contentTypeDer Content Type, über den iteriert wird ({ sys: { id } }). Bei Content erforderlich. Bei Media und ServiceUser ignoriert
whereDer 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
orderDie Sortierung (z. B. "sys.createdAt,sys.id"). Fehlt sie, gilt die Standardreihenfolge der Plattform
fromCurrent (Standard, neuester Entwurf) oder Published (veröffentlichter Snapshot). Bei ServiceUser nur Current
advancedIteration ü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
onEachDas Array von Kind-Statements, das für jedes Element ausgeführt wird (erforderlich)
  • Bindet keine Collection (es ist foreach, kein map). Es gibt weder { items, next } noch einen cursor. Man erhält das Iterationsergebnis nicht als Wert zurück, sondern führt pro Element onEach aus. Wenn Sie eine Liste benötigen, sammeln Sie sie selbst mit SetVar. Wenn Sie nur die Anzahl benötigen, verwenden Sie ResourceCount.
  • Auch ohne limit ist es keine Endlositeration. Ohne limit lä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 deklarierten limit ein beabsichtigter Halt und daher eine normale Beendigung. Ein limit ü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 man where auf „unbearbeitet" und markiert am Ende von onEach die 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 onEach deklarierte 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 in onEach auf das Budget angerechnet.
  • onEach kann wie jedes andere Statement externe Aufrufe (Http, EmailSend) oder einen Media-Datei-Ingest enthalten (wie der body bei Loop). 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.

FeldBeschreibung
resource"Content" oder "ContentType" (erforderlich). Media und ServiceUser lassen sich nicht zählen; steht einer dieser Werte dort, wird das Speichern abgelehnt
contentTypeDer 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)
whereDer 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) oder ResourceForEach (Ausführung pro Element).
  • Zählen Sie nicht, indem Sie für eine Anzahl mit ResourceForEach iterieren. 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.
  • order und limit gibt 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. Zum Zeitbudget wird timeoutMs addiert, und werden Dateien gesendet, kommt für jede Datei die Downloadzeit hinzu. Ist retry angegeben, wird diese Summe mit der Anzahl der Versuche (1 + retry) multipliziert.

FeldBeschreibung
method"GET", "POST", "PUT", "PATCH", "DELETE"
urlDie Ziel-URL (Wertausdruck; { /ptr } kann eingefügt werden)
headers[{ "key", "value" }]. value ist ein Wertausdruck. Geheimwerte wie API-Schlüssel schreiben Sie nicht direkt hinein, sondern deklarieren sie in secrets und lesen sie als { /secrets/<key> } (Beispiel: "Bearer { /secrets/LLM_API_KEY }"). Setzen Sie hier Content-Type, wird body in dieses Format serialisiert (unten)
bodyDer Anfrage-Body (Wertausdruck oder JSON). In welchem Format er übertragen wird, bestimmt der Content-Type-Header
urlBodyDie URL der Datei, die als Anfrage-Body gesendet wird (Wertausdruck). Die Plattform lädt die Datei von dieser URL und sendet ihre Bytes unverändert (Eine Datei als Body senden)
multipartBodyDas Array der Parts, die als multipart/form-data gesendet werden (mindestens 1). Text-Parts und Datei-Parts lassen sich mischen (Eine Datei als Body senden)
timeoutMsDas Timeout für diesen Aufruf (ms)
retryAnzahl der Wiederholungen, wenn der Antwort-Status 400 oder höher ist. Standard 0; die Obergrenze ist 2
ignoreStatusCodeOb 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)
responseTypeIn welcher Form der Antwort-Body übergeben wird. "Json" (Standard) parst ihn zu einem Objekt oder Array, "Text" übergibt ihn als String
  • Verwenden Sie von body, urlBody und multipartBody nur eines. Stehen zwei oder mehr davon, wird das Speichern abgelehnt. Fehlen alle drei, wird ohne Body gesendet.
  • Ergebnis: { status, body }. Haben Sie diesem Statement einen name gegeben, dann { /<name>/status } und { /<name>/body/... }. responseType bestimmt die Form von body.
  • responseType gilt 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 (ein Try/catch-Ziel). Bei einer API, die kein JSON liefert, empfangen Sie den Body mit "Text" und parsen ihn mit ParseJson, wenn Sie ihn als Wert brauchen.
  • "Text" wird mit dem Charset des Content-Type der Antwort dekodiert und ohne Charset als UTF-8 behandelt. Ist der Body leer, ist body in beiden Fällen null.
  • 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/catch behandelt werden (dies ist ein größenbasierter Fehlschlag und wird daher nicht durch ignoreStatusCode unterdrückt).
{ "type": "Http", "method": "POST", "url": "https://api.llm.com/v1/gen",
  "headers": [ { "key": "Authorization", "value": "Bearer { /secrets/LLM_API_KEY }" } ],
  "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-TypeForm von bodyGesendeter body
application/jsonBeliebigJSON
application/x-www-form-urlencodedObjekt oder Arrayorder[id]=A-2481&order[amount]=34000
text/plainSkalarDer Wert unverändert
Sonstige (z. B. text/xml)BeliebigJSON

Diese Kombinationen werden korrigiert, weil sie im deklarierten Format nicht abgebildet werden können.

Deklarierter Content-TypeForm von bodyTatsächlich gesendeter Content-TypeGesendeter body
application/x-www-form-urlencodedSkalartext/plain;charset=UTF-8Der Wert unverändert
text/plainObjekt oder Arrayapplication/jsonJSON

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.

bodyEntfaltete 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" }

Eine Datei als Body senden

Manchmal muss eine Datei, die in Media hochgeladen wurde, an eine externe API weitergegeben werden. Die Bytes einer Datei lassen sich nicht in einem Wertausdruck unterbringen. Geben Sie stattdessen die URL an, unter der die Datei liegt; die Plattform lädt die Datei von dieser URL und sendet sie als Anfrage-Body. Um eine einzelne Datei als gesamten Body zu senden, verwenden Sie urlBody; um Dateien zusammen mit Textfeldern als Formular zu senden, verwenden Sie multipartBody.

urlBody: Eine einzelne Datei als gesamten Body senden

Sendet die von der angegebenen URL geladene Datei ohne Umwandlung unverändert als Anfrage-Body. Das eignet sich für APIs, bei denen eine Datei per PUT an eine Upload-URL hochgeladen wird. Das folgende Beispiel liest ein Vertrags-PDF (Media) und sendet es an die Upload-URL eines Dienstes für elektronische Signaturen.

// Teil des statements-Arrays
{ "type": "ResourceRead", "resource": "Media",
  "target": { "sys": { "id": "{ /payload/fields/contractMediaId }" } }, "name": "contract" },
{ "type": "Http", "method": "PUT",
  "url": "https://api.esign.example/v1/envelopes/{ /payload/fields/envelopeId }/document",
  "headers": [ { "key": "Authorization", "value": "Bearer { /secrets/ESIGN_API_KEY }" },
               { "key": "Content-Type", "value": "application/pdf" } ],
  "urlBody": "{ /contract/fields/file/en-US/url }", "name": "upload" }

Der Content-Type der Anfrage wird in folgender Reihenfolge bestimmt.

  1. Ist in headers ein Content-Type angegeben, wird dieser Wert unverändert verwendet. Die Korrektur aus der body-Serialisierung wird nicht angewendet.
  2. Ist keiner angegeben oder ist sein Wert leer, wird der Content-Type verwendet, mit dem die Datei-URL geantwortet hat.
  3. Fehlt auch dieser, wird application/octet-stream verwendet.

multipartBody: Dateien und Text als Formular senden

Sendet als multipart/form-data. Jedes Element des Arrays wird zu einem Part, und die Parts werden in der Reihenfolge des Arrays übertragen. Das Array muss mindestens 1 Element enthalten; bei einem leeren Array wird das Speichern abgelehnt. Der Content-Type ist immer multipart/form-data (der Boundary-String wird automatisch erzeugt), und ein in headers angegebener Content-Type wird ignoriert.

Part-FeldBeschreibung
nameDer Name des Formularfelds (erforderlich). Kein Wertausdruck, sondern ein Literal. Wird derselbe Name mehrfach verwendet, geht jeder als eigener Part hinaus
valueDer Inhalt eines Text-Parts (Wertausdruck). Ist das Ergebnis leer, wird ein leerer Wert gesendet
urlDie URL, von der die Bytes eines Datei-Parts geladen werden (Wertausdruck). Beispiel: die url einer Media-Datei
fileNameDer Dateiname, der im Content-Disposition des Parts steht (Wertausdruck). Nur bei einem url-Part verwendbar
contentTypeDer Content-Type des Parts (Wertausdruck)
  • Geben Sie genau eines von value und url an. Stehen beide oder fehlen beide, wird das Speichern abgelehnt. Auch ein fileName bei einem value-Part führt dazu, dass das Speichern abgelehnt wird.
  • Wird fileName weggelassen, wird das letzte Pfadsegment der url dekodiert verwendet (ohne Query-String). Ist dieses Segment leer oder enthält es Steuerzeichen, wird file gesendet.
  • Wird contentType weggelassen, geht ein value-Part ohne Content-Type hinaus, und ein url-Part verwendet den Content-Type, mit dem die Datei-URL geantwortet hat (fehlt dieser, application/octet-stream). Geben Sie einen Wert an, der kein Medientyp-Format hat, schlägt dieses Http bei der Ausführung mit Status 400 fehl.
  • Enthält das Ergebnis des Wertausdrucks von fileName oder contentType einen Zeilenumbruch, schlägt dieses Http vor dem Senden der Anfrage mit Status 400 fehl.

Das folgende Beispiel sendet ein Produktfoto (Media) zusammen mit Analyseoptionen an einen Bildanalysedienst. Beim Part image ist kein fileName angegeben, daher geht das letzte Segment der Media-Datei-URL (z. B. tumbler.png) als Dateiname hinaus.

// Teil des statements-Arrays
{ "type": "ResourceRead", "resource": "Media",
  "target": { "sys": { "id": "{ /payload/fields/photoMediaId }" } }, "name": "photo" },
{ "type": "Http", "method": "POST", "url": "https://api.vision.example/v2/analyze",
  "headers": [ { "key": "x-api-key", "value": "{ /secrets/VISION_API_KEY }" } ],
  "multipartBody": [
    { "name": "image", "url": "{ /photo/fields/file/en-US/url }" },
    { "name": "language", "value": "ko" },
    { "name": "features", "value": "labels" },
    { "name": "features", "value": "text" }
  ], "timeoutMs": 20000, "name": "analysis" }

Auch die Fehlschläge eines Http, das Dateien sendet, teilen sich in zwei Arten. Eine Zeitüberschreitung wird als Antwort mit Status 504 behandelt, daher gelten retry und ignoreStatusCode. Für die dateibezogenen Fehlschläge in der Tabelle unten gilt keines von beiden. Die Einteilung aller Http-Fehlschläge finden Sie unter Die zwei Arten, wie Http fehlschlägt.

Bei ignoreStatusCode: true wird bei einer Zeitüberschreitung { "status": 504, "body": "timed out after …ms" } als Ergebnis gebunden.

Eine einzelne Anfrage muss innerhalb von timeoutMs abgeschlossen sein, und die Zeit, die auf die Antwort der Datei-URL gewartet wird, ist darin enthalten. Werden Dateien gesendet, verlängert sich das Zeitlimit um die Zeit, die das Senden dieser Dateien mit einer Geschwindigkeit von 10.000.000 Byte pro Sekunde dauert. Die Verlängerung überschreitet nicht die Downloadzeit des Tarifs multipliziert mit der Anzahl der Dateien. Teilt die Datei-URL die Dateigröße jedoch nicht mit, verlängert sich das Limit nur um die tatsächlich gesendete Menge; ist die Übertragung langsam oder bleibt sie unterwegs stehen, kann die Zeit daher früher überschritten werden.

In den folgenden Fällen schlägt dieses Http sofort fehl. Es wird nicht mit retry wiederholt und auch bei ignoreStatusCode: true als Fehlschlag behandelt. Wie jeder andere Laufzeitfehlschlag lässt es sich mit Try/catch behandeln.

SituationErgebnis
Die zu ladende Datei ist größer als das Download-Limit des TarifsSchlägt mit Status 413 fehl. Teilt die Datei-URL die Größe vorab mit, geschieht das vor dem Laden; andernfalls in dem Moment, in dem beim Laden das Limit überschritten wird
Die Datei-URL antwortet mit einem Status, der nicht 2xx ist (einschließlich Weiterleitungen), oder zur Datei-URL kann keine Verbindung hergestellt werdenSchlägt mit Status 502 fehl
Der contentType eines Parts hat kein Medientyp-FormatSchlägt mit Status 400 fehl
Der Wert von fileName oder contentType eines Parts enthält einen ZeilenumbruchSchlägt mit Status 400 fehl. Der Fehlschlag tritt ein, bevor die Anfrage gesendet wird
  • Das Download-Limit gilt je Datei-URL. Enthält multipartBody mehrere Datei-Parts, gilt das Limit für jede Datei gesondert.
  • Die gesendeten Bytes werden einschließlich der Dateien auf „Externe Aufrufe" bei der Datenübertragung im Tarif angerechnet.

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.

FeldBeschreibung
accountDie 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
toDie Empfängeradresse (Wertausdruck). Von to und toServiceUser verwenden Sie genau eines
toServiceUserGibt 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
ccArray von Cc-Empfängeradressen (Wertausdruck)
bccArray von Bcc-Empfängeradressen (Wertausdruck)
subjectDer Betreff (Wertausdruck, erforderlich)
bodyDer Rumpf (Wertausdruck). 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, sofern autoEscape nicht false ist. Von body und templateBody verwenden Sie genau eines
templateBodyErstellt den Rumpf aus einer Vorlage. Hat die Form { source, data }; davon und von body verwenden Sie genau eines. Siehe unten Den Rumpf aus einer Vorlage erstellen
autoEscape(Optional, Standard true) Ob die in den Rumpf interpolierten Werte HTML-escaped werden. Betroffen sind die Ergebnisse von { /pointer } in body und die Werte von templateBody.data. Bei false wird Markup, das in einem Wert steht, unverändert gerendert. Das vom Autor selbst geschriebene Markup und die Vorlage aus templateBody.source sind von vornherein nicht betroffen, und Header-Felder bleiben ebenfalls unberührt
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
  • Den Rumpf schreiben Sie entweder mit body oder mit templateBody. Werden beide zugleich angegeben oder bleiben beide leer, wird das Speichern abgelehnt.
  • Werte, die der Aufrufer gesendet hat, werden standardmäßig als Zeichen gerendert. Ist autoEscape eingeschaltet, erscheint Markup wie <a> in einem Wert als Zeichen und nicht als Tag. Das ist die Vorkehrung dagegen, dass ein von außen gesendeter Wert Links oder Tags in die E-Mail einer anderen Person einschleust. Setzen Sie es nur dann auf false, wenn das Markup erhalten bleiben muss, etwa beim Newsletter-Rumpf, den ein Administrator selbst geschrieben hat.
  • In Header-Felder dürfen keine Werte mit Zeilenumbruch eingetragen werden. Das betrifft subject, replyTo und alle Adressen. Kommt ein Zeilenumbruch herein, schlägt die Ausführung fehl. Im Rumpf ist ein Zeilenumbruch gewöhnlicher Inhalt und wird daher nicht geprüft.
  • Die Summe der Empfänger beträgt höchstens 50. Sie zählt to (1), cc und bcc zusammen (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 mit ResourceForEach + EmailSend je 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 dem retry von Http). Ein Fehlschlag wird geworfen und über das catch von Try behandelt.
  • 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 bei Http multipliziert). Es lässt sich innerhalb von onEach bei ResourceForEach verwenden (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" }

Den Rumpf aus einer Vorlage erstellen (templateBody)

Schreiben Sie den E-Mail-Rumpf direkt in die Script-Definition, müssen Sie bei jeder Textänderung das Script ändern. templateBody legt die Rumpfvorlage in einem Content ab und lässt Sie nur deren Lücken füllen. So ändern Sie den Text im Content Studio und lassen das Script unverändert.

SchlüsselBeschreibung
sourceGenau ein Pointer auf die Stelle, an der die Vorlage liegt (erforderlich). Er muss auf ein Content zeigen, das ein ResourceRead oder ein ResourceFind dieses Script gelesen hat, und vor oder hinter diesem Pointer dürfen keine weiteren Zeichen stehen. Der Wert muss eine Zeichenkette sein, bei einem mehrsprachigen Field geben Sie daher auch die Locale an (/tpl/fields/html/en-US)
dataDie Werte, die die Lücken der Vorlage füllen (optional). Der Schlüsselname ist zugleich der Name der Lücke. Da es eine Datenstelle ist, stellen Sie einem Operator ein $ voran ($cat), und eine Lücke, deren Name mit $ beginnt, schreiben Sie als $$ (Datenstelle und Ausdrucksstelle)

Eine Lücke in der Vorlage schreiben Sie als { /<Schlüssel> }. Diese Lücke liest ausschließlich data; aus der Vorlage heraus lassen sich /payload oder /secrets nicht direkt lesen. Eine Lücke, die auf einen in data nicht vorhandenen Schlüssel zeigt, wird als leere Zeichenkette gerendert. Dasselbe gilt für einen Schlüssel, dessen Wert null ist. Das ist das Gegenteil davon, dass ein Pointer in body, der auf null zeigt, die Zeichen null ausgibt. Achten Sie darauf, wenn Sie zwischen beiden Schreibweisen wechseln.

Steht in der Vorlage an einer Stelle hinter { ein / (auch mit Leerzeichen dazwischen, Beispiel: a{/*Kommentar*/} in CSS), wird das als Lücke gelesen. Schreiben Sie es daher als \{.

An drei Stellen geht das Speichern durch, die Ausführung bricht aber ab. Löst sich source zu einem Objekt auf (wenn bei einem mehrsprachigen Field die Locale fehlt), schlägt es fehl. Ist source ein leerer Wert, schlägt es fehl (das ResourceFind hat nichts gefunden, oder das Statement, das die Vorlage liest, steht hinter dem EmailSend). Besteht das Ergebnis nach dem Füllen aller Lücken nur aus Leerzeichen, schlägt es ebenfalls fehl.

Die von source bezeichnete Vorlage selbst wird nicht escaped. Escaped werden die Werte aus data. Wollen Sie daher HTML, das der Aufrufer gesendet hat, in der E-Mail erhalten, setzen Sie diesen Wert in data und autoEscape auf false.

{ "type": "ResourceRead", "resource": "Content", "name": "tpl",
  "target": { "sys": { "id": "cnt_mail_shipping" } } },
{ "type": "EmailSend", "account": { "sys": { "id": "eml_orders" } },
  "toServiceUser": { "sys": { "id": "{ /order/fields/buyer/en-US/sys/id }" } },
  "subject": "Der Versand hat begonnen",
  "templateBody": {
    "source": "{ /tpl/fields/html/en-US }",
    "data": { "name": "{ /order/fields/buyerName/en-US }",
              "tracking": "{ /order/fields/trackingNo/en-US }" } } }

Die Vorlage (das Field html von cnt_mail_shipping) sieht so aus.

<p>Hallo { /name }, der Versand der von Ihnen bestellten Ware hat begonnen.</p>
<p>Sendungsnummer: { /tracking }</p>

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).

FeldBeschreibung
varDer Variablenname. Referenziert als { /vars/<var> }
valueEin 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 sammeln

Zwischenspeicher

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.

FeldBeschreibung
actionEines von "Set" (schreiben), "Get" (lesen) und "Delete" (entfernen) (erforderlich)
keyDer Cache-Key (erforderlich). Kein Wertausdruck, sondern ein Literal (siehe unten). Maximal 128 Zeichen; darüber wird das Speichern abgelehnt
valueDer zu speichernde Wert (nur Set)
ttlDie Zeit, die der Cache lebt (nur Set, in Sekunden). Zwischen 1 und 30; ohne Angabe 5
defaultValueDer Wert, den Get bindet, wenn keine zwischengespeicherten Daten vorhanden sind (nur Get). Ohne Angabe null
nameDer 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 Get ein ttl oder bei Set ein defaultValue, wird das Speichern abgelehnt.
  • Nicht vorhanden und abgelaufen werden nicht unterschieden. In beiden Fällen wird defaultValue gebunden. Dasselbe gilt, wenn null gespeichert 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.
  • key ist 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 } in key, wird er weder zu einem Wert noch buchstäblich verwendet: Das Speichern selbst wird abgelehnt.
  • Innerhalb einer Iteration ist es nicht erlaubt. Steht ein Cache im Block von Loop oder ResourceForEach, 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/catch lokal 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.

FeldBeschreibung
nameDer 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
valueDer 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 42 oder "a" wird ebenso geparst. Danach adressieren Sie das Innere mit { /<name>/... }.
  • Ein Wert, der bereits geparst ist, wird unverändert gebunden. Löst value zu 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.
  • null deckt zwei verschiedene Fälle ab. Ist der zu parsende Text das einzelne Wort null, ist das normal und das Ergebnis ist null. Ist die Stelle, auf die value zeigt, dagegen leer, gibt es also überhaupt keinen Wert, dann ist nichts zu parsen und das Statement schlägt fehl.
  • Fehlschlag: wenn value zu keinem Wert oder nur zu Leerzeichen auflöst, und wenn der Text kein JSON ist. Behandeln Sie ihn wie jeden anderen Laufzeitfehlschlag mit Try/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.

FeldBeschreibung
nameDer 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
algorithmDer Hash, mit dem der Code erzeugt wird (erforderlich). SHA1, SHA256, SHA384, SHA512
secretDer mit der Gegenseite geteilte geheime Schlüssel (Wertausdruck, erforderlich). Den Schlüssel deklarieren Sie in secrets und schreiben ihn als { /secrets/<key> }
secretEncodingIn welcher Notation die Schlüsselzeichenkette vorliegt, auf die secret zeigt. 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
valueDie 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
expectedDer vom Aufrufer gesendete Code (Wertausdruck, erforderlich). Beispiel: { /headers/x-signature }
  • Ergebnis: ein Boolean. Danach verwenden Sie { /<name> } unverändert als Bedingung eines If.
  • Schreiben Sie für value nicht 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. algorithm legt 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 false ist, entscheidet sich daran, wer den Wert liefert.
    • Fehlt expected oder passt der Code nicht, ist das Ergebnis lediglich false und 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 value leer, wird mit einer leeren Nachricht gerechnet. Auch ein leerer Body ist Gegenstand einer Signatur.
    • Fehlt secret oder entspricht es nicht der in secretEncoding deklarierten Notation, ist es ein Fehlschlag. Von den drei Fällen ist dies die einzige Eingabe des Autors selbst. In der Fehlermeldung werden secret und value nicht mitgeführt.
  • Die Obergrenze für value beträ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.
  • Den Signaturschlüssel schreiben Sie nicht direkt in secret. Deklarieren Sie ihn in secrets der Definition und schreiben Sie ihn als "{ /secrets/<key> }". Die Werte in secrets speichert WEEGLOO verschlüsselt und sicher. secretEncoding gibt an, in welcher Notation die in secrets gespeicherte Schlüsselzeichenkette vorliegt.
// ein Anbieter, der den gesamten Body signiert
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
  "secret": "{ /secrets/WEBHOOK_SIGNING_SECRET }", "value": "{ /rawPayload }",
  "expected": "{ /headers/x-webhook-signature }" }
 
// ein Anbieter, der den Schlüssel als base64 ausgibt (der base64-String steht unverändert in secrets)
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
  "secret": "{ /secrets/WEBHOOK_SIGNING_KEY }", "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".

FeldBeschreibung
nameDer Name, der den Digest aufnimmt (erforderlich)
algorithmMD5, 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
valueDie zu hashende Nachricht (Wertausdruck, erforderlich)
encodingDie Notation des Ergebnisses. Hex (Standard), HexUpper, Base64, Base64Url
  • Es gibt kein secret-Feld. Schreiben Sie { /secrets/<key> } innerhalb von value an die Stelle, an die dieses Schema den Schlüssel setzt. Der Schlüssel steht je Schema vorne, hinten oder in der Mitte; auf diese Weise lässt sich jede dieser Positionen ausdrücken. Den Schlüssel deklarieren Sie vorab in secrets.
  • 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 von Signature ein normaler Gleichheitsvergleich.
  • Löst value zu keinem Wert oder nur zu Leerzeichen auf, ist es ein Fehlschlag (denn es ist ein Ausdruck des Autors selbst).
  • Die Obergrenze für value beträgt 128 Zeichen. Da es die Stelle für einige aneinandergehängte Felder ist, ist sie viel enger als bei Signature. Müssen Sie über den gesamten Webhook-Body rechnen, verwenden Sie Signature.
// SHA256(Bestellnummer + Betrag + MERCHANT_KEY) als Hex in Großbuchstaben
{ "type": "Hash", "name": "expectedSign", "algorithm": "SHA256", "encoding": "HexUpper",
  "value": "{ /payload/orderId }{ /payload/amount }{ /secrets/MERCHANT_KEY }" }

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.

FeldBeschreibung
nameDer 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)
patternDer 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
valueDer 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: Match ist ein Boolean, Capture ist ein Array oder null. Im Array ist Index 0 der gesamte Treffer, ab 1 folgen die Capture-Gruppen, und eine Gruppe, die nicht beteiligt war, ist null (kein leerer String, denn der wäre ein Treffer). Tritt das Pattern nicht auf, ist Capture kein leeres Array, sondern null.
  • 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 mit Match prüfen und dann mit Capture herausholen, nicht unterschiedliche Antworten geben.
  • pattern ist eines der drei Felder dieser Engine, die kein Wertausdruck sind (die anderen sind das key von Cache und das color von PdfWatermark). 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 Loop oder ResourceForEach wird es nicht bei jeder Iteration erneut kompiliert, und ein unbrauchbares Pattern schlägt fehl, bevor das erste Statement irgendetwas tut (mit Try behandelbar).
// "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.

FeldBeschreibung
conditionJsonLogic (als Boolean ausgewertet)
thenDas 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.

FeldBeschreibung
overforeach: ein Wertausdruck, der zu einem Array aufgelöst wird
whileBedingung: JsonLogic (wiederholt, solange true)
forZählung: { "from", "to", "step"? }. Von from bis to inklusive; step standardmäßig 1
maxIterationsMaximale 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> })
bodyDas 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).

FeldBeschreibung
branchesStatement[][]. 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.

FeldBeschreibung
value(Optional) Der zurückzugebende Wertausdruck
isErrorStandard false. Bei true kommt value als error der Antwort zurück (andernfalls als return)
statusCodeDer Antwort-Statuscode. Standard 200
  • Wird Return nie erreicht, gibt es keinen Rückgabewert. Um ein Ergebnis zurückzugeben, geben Sie value explizit an.
  • Da es sich um eine normale Beendigung handelt, nicht um eine Ausnahme oder einen throw, ist es kein catch-Ziel (auch innerhalb von Try beendet es das gesamte Script, aber finally wird dennoch ausgeführt).
  • Auch ein Guard wird mit diesem Statement ausgedrückt. Setzen Sie ein Return in das then eines If, 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 von Return.
{ "type": "Return", "value": { "orderId": "{ /order/sys/id }", "status": "paid" }, "statusCode": 201 }
{ "type": "Return", "value": { "reason": "payment failed" }, "isError": true, "statusCode": 402 }

Try

Ausnahmebehandlung.

FeldBeschreibung
bodyDas 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 catch den Fehler behandelt, wird das Script nicht abgebrochen. Nur ein Fehlschlag ohne catch bricht 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 */ ] }