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
| 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 }). Kann eine Datei als Body senden |
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 Statement, das Content bearbeitet und sein Ziel nicht per id angibt, muss immer den Content Type nennen. 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,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.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.
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.
| 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", "transformations": [ … ] }.sourceundencodingsind erforderlich;transformationsgeben 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 (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" }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-Anweisung | Erforderlich | Beschreibung |
|---|---|---|
transformations | Optional | Ein 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,ResourceUpdateundResourcePatchangegeben werden. - Das Original, auf das
sourcezeigt, bleibt unverändert. Um Text auf eine Datei aufzudrucken, die bereits im Space liegt, lesen Sie das Media mitResourceRead, setzen{ /<name>/fields/file/<locale>/url }insourceein 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
| Feld | Erforderlich | Beschreibung |
|---|---|---|
type | Erforderlich | "PdfWatermark". Bei einem anderen Wert wird das Speichern des Script abgelehnt |
text | Erforderlich | Der 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 |
color | Optional | Die 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).
| Art | Zeichen |
|---|---|
| Druckbare ASCII-Zeichen | Leerzeichen, lateinische Groß- und Kleinbuchstaben, Ziffern und grundlegende Symbole wie @ . # { } |
| Latin-1-Zeichen | Westeuropä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änkung | Literales text | text mit { /pointer } |
|---|---|---|
| Eine einzelne Zeile aus zulässigen Zeichen | Speichern des Script abgelehnt | Beim 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 Zeichen | Speichern des Script abgelehnt | Beim 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.
| Situation | Ergebnis |
|---|---|
Die Datei aus source ist kein PDF | Schlä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 Leerzeichen | Schlägt mit Status 400 fehl |
Das aufgelöste text liegt außerhalb von 1-64 Zeichen oder enthält unzulässige Zeichen | Schlä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.
| Feld | Beschreibung |
|---|---|
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 |
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). Ein ServiceUser nimmt es nicht an |
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, -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.
| Feld | Beschreibung |
|---|---|
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 |
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). Ein ServiceUser nimmt es nicht an |
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 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.
ResourceUpdateundResourcePatchnehmen"ServiceUser"an und ändernroleOverrideundenableLogin. Übergeben Sie beiResourcePatchfürroleOverrideeinnull, wird die zugewiesene Rolle aufgehoben.ResourceCreateundResourceDeletesowie 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
versionnochpropagateEvents. Geben Sie eines von beiden an, schlägt die Ausführung fehl.ResourceUpdateersetzt alle schreibbaren Felder, daher istenableLoginerforderlich, und wenn SieroleOverrideweglassen, wird die zugewiesene Rolle aufgehoben. Um nur eines von beiden zu ändern, verwenden SieResourcePatch; 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
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 (Mit dem Mitgliederverzeichnis arbeiten) |
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. 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.
| Feld | Beschreibung |
|---|---|
method | "GET", "POST", "PUT", "PATCH", "DELETE" |
url | Die 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) |
body | Der Anfrage-Body (Wertausdruck oder JSON). In welchem Format er übertragen wird, bestimmt der Content-Type-Header |
urlBody | Die 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) |
multipartBody | Das 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) |
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 |
- Verwenden Sie von
body,urlBodyundmultipartBodynur eines. Stehen zwei oder mehr davon, wird das Speichern abgelehnt. Fehlen alle drei, wird ohne Body gesendet. - 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 { /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-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" }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.
- Ist in
headerseinContent-Typeangegeben, wird dieser Wert unverändert verwendet. Die Korrektur aus der body-Serialisierung wird nicht angewendet. - Ist keiner angegeben oder ist sein Wert leer, wird der
Content-Typeverwendet, mit dem die Datei-URL geantwortet hat. - Fehlt auch dieser, wird
application/octet-streamverwendet.
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-Feld | Beschreibung |
|---|---|
name | Der Name des Formularfelds (erforderlich). Kein Wertausdruck, sondern ein Literal. Wird derselbe Name mehrfach verwendet, geht jeder als eigener Part hinaus |
value | Der Inhalt eines Text-Parts (Wertausdruck). Ist das Ergebnis leer, wird ein leerer Wert gesendet |
url | Die URL, von der die Bytes eines Datei-Parts geladen werden (Wertausdruck). Beispiel: die url einer Media-Datei |
fileName | Der Dateiname, der im Content-Disposition des Parts steht (Wertausdruck). Nur bei einem url-Part verwendbar |
contentType | Der Content-Type des Parts (Wertausdruck) |
- Geben Sie genau eines von
valueundurlan. Stehen beide oder fehlen beide, wird das Speichern abgelehnt. Auch einfileNamebei einemvalue-Part führt dazu, dass das Speichern abgelehnt wird. - Wird
fileNameweggelassen, wird das letzte Pfadsegment derurldekodiert verwendet (ohne Query-String). Ist dieses Segment leer oder enthält es Steuerzeichen, wirdfilegesendet. - Wird
contentTypeweggelassen, geht einvalue-Part ohneContent-Typehinaus, und einurl-Part verwendet denContent-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 diesesHttpbei der Ausführung mit Status 400 fehl. - Enthält das Ergebnis des Wertausdrucks von
fileNameodercontentTypeeinen Zeilenumbruch, schlägt diesesHttpvor 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.
| Situation | Ergebnis |
|---|---|
| Die zu ladende Datei ist größer als das Download-Limit des Tarifs | Schlä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 werden | Schlägt mit Status 502 fehl |
Der contentType eines Parts hat kein Medientyp-Format | Schlägt mit Status 400 fehl |
Der Wert von fileName oder contentType eines Parts enthält einen Zeilenumbruch | Schlägt mit Status 400 fehl. Der Fehlschlag tritt ein, bevor die Anfrage gesendet wird |
- Das Download-Limit gilt je Datei-URL. Enthält
multipartBodymehrere 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.
| 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). 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 |
templateBody | Erstellt 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
bodyoder mittemplateBody. 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
autoEscapeeingeschaltet, 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 auffalse, 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,replyTound 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),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" }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üssel | Beschreibung |
|---|---|
source | Genau 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) |
data | Die 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).
| 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). Den Schlüssel deklarieren Sie in secrets und schreiben ihn als { /secrets/<key> } |
secretEncoding | In 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 |
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.
- Den Signaturschlüssel schreiben Sie nicht direkt in
secret. Deklarieren Sie ihn insecretsder Definition und schreiben Sie ihn als"{ /secrets/<key> }". Die Werte insecretsspeichert WEEGLOO verschlüsselt und sicher.secretEncodinggibt an, in welcher Notation die insecretsgespeicherte 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".
| 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. Schreiben Sie{ /secrets/<key> }innerhalb vonvaluean 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 insecrets. - 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 + 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.
| 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 drei Felder dieser Engine, die kein Wertausdruck sind (die anderen sind daskeyvonCacheund dascolorvonPdfWatermark). 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.
