Catálogo de Statements
Cada elemento do array statements é um único Statement. Este documento cataloga os campos, o comportamento e o resultado dos 25 tipos de Statement. Toda posição de valor segue as regras de Expressões de valor (referência, literal, JsonLogic, mapa de locale). As exceções são três: o pattern de Regex, a key de Cache e a color de PdfWatermark (veja Regex, Cache e Transformação de arquivos).
Resumo dos Statements
| Categoria | type | Resumo em uma linha |
|---|---|---|
| Escritas de recurso | ResourceCreate | Cria Content/Media (opcionalmente publica) |
ResourceUpdate | Substituição total dos campos de Content/Media (todo campo/locale não fornecido é excluído) | |
ResourcePatch | Mesclagem parcial dos campos de Content/Media (apenas os campos/locales especificados; um null literal exclui) | |
ResourceDelete | Exclui (apenas Draft/Archived; se Published, anule a publicação primeiro) | |
ResourcePublish / ResourceUnpublish | Publica / anula a publicação | |
ResourceArchive / ResourceUnarchive | Arquiva / desarquiva | |
| Leituras de recurso | ResourceRead | Lê um único item por id |
ResourceFind | Primeiro item único correspondente por filtro (null se não houver) | |
ResourceForEach | Percorre internamente os recursos que atendem ao filtro e executa onEach em cada item | |
ResourceCount | Conta apenas a quantidade de itens que atendem ao filtro (não lê os itens) | |
| Externo | Http | Chamada HTTP externa ({ status, body }). Pode enviar um arquivo como corpo |
EmailSend | Envia 1 e-mail por meio de um EmailAccount registrado | |
| Variáveis | SetVar | Declara/atualiza uma variável com escopo de script |
| Armazenamento em cache | Cache | Lê/grava/remove no cache de vida curta exclusivo daquele Script |
| Parse de valores | ParseJson | Faz o parse de texto JSON em um valor (objeto, array, escalar) e o vincula |
| Assinatura e texto | Signature | Verifica se o código de assinatura recebido é igual ao código gerado com a chave secreta (Boolean) |
Hash | Calcula um digest sem chave (string) | |
Regex | Aplica uma expressão regular. Se houve correspondência (Boolean) ou os grupos de captura (array) | |
| Controle de fluxo | If | Ramificação condicional |
Loop | Itera (foreach / while / counted) | |
Parallel | Executa branches de forma concorrente | |
Return | Retorna um resultado e encerra antecipadamente | |
Try | Tratamento de exceções (catch/finally) |
Todo Statement de Content que não indica o alvo por id precisa declarar o Content Type com que trabalha. Em
ResourceFind,ResourceForEach,ResourceCount, quandoresourceé"Content", ocontentTypeé obrigatório. Não existe consulta de Content que atravesse o Space inteiro. OResourceCreatetambém declara o Content Type que vai criar. Media não delimita escopo, porque o Space inteiro é um único conjunto, e os Statements que indicam o alvo por id (ResourceRead,ResourceUpdate,ResourcePatch,ResourceDeletee os Statements de publicação e arquivamento) têmtarget, portanto não precisam de escopo.
Chamadas cíclicas são limitadas a 3. Se você ativar
propagateEventsnos Statements de escrita de recursos acima (ResourceCreate,ResourceUpdate,ResourcePublishetc.), cujo padrão é vir desativado, eles geram eventos de alteração, e esses eventos podem executar um Script novamente por meio de um Webhook. Uma cadeia assim (Script → evento → Webhook → Script → …) continua no máximo 3 vezes. A partir daí, ela é interrompida automaticamente para evitar loops infinitos.
Campos comuns
{ "type": "<StatementType>", "name": "<opcional, único dentro do script>", /* ...campos específicos do tipo... */ }type: o discriminador. Um dos valores da tabela acima (obrigatório).name: opcional. Se definido, o resultado é vinculado ao contexto em/<name>, de modo que Statements posteriores podem referenciá-lo como{ /<name>/... }. Omita-o se você não usar o resultado.- Regras de nome de vínculo:
nameé uma chave adicionada diretamente à raiz do contexto, por isso é validada ao salvar. Ela só pode usar letras ASCII (a-z, A-Z), dígitos,_e-(precisa poder servir como chave de JSON Pointer, portanto qualquer outro caractere, ou um nome vazio, é rejeitado), não pode coincidir com as raízes reservadas (payload,rawPayload,headers,vars,error,now,secrets) e deve ser única dentro de um Script. Em caso de violação de formato, uso de palavra reservada ou duplicidade, o salvamento é rejeitado.
Formato da referência de entidade
Referências de entidade como contentType e target são unificadas em um único formato: { "sys": { "id": <expressão de valor> } }. Apenas sys.id é necessário; o tipo de destino é inferido a partir de resource (sys.type e sys.targetType são omitidos).
contentType.sys.idgeralmente é um literal (ex.:"ct_post").target.sys.idgeralmente é uma expressão de valor{ /ptr }(resolvida em tempo de execução; ex.:{ /payload/sys/id }).
resource
Os Statements da família de recursos especificam o tipo de destino com resource: "Content" | "ContentType" | "Media" | "ServiceUser".
Content Type é aceito somente pelo ResourceCount. Se você o escrever em outro Statement, o salvamento é rejeitado. Criar ou alterar o próprio molde é da alçada da CMA, e não do Script.
Os Statements que aceitam ServiceUser (o membro inscrito no produto) são os três de leitura (ResourceRead, ResourceFind, ResourceForEach) e ResourceUpdate, ResourcePatch. Os únicos campos que esses dois podem escrever são roleOverride e enableLogin. Se você o escrever em qualquer outro Statement de escrita, o salvamento é rejeitado (consulte Erros). As regras são abordadas em Trabalhar com o diretório de membros.
Escritas de recurso
Todo Statement de escrita tem propagateEvents (padrão false) (o ResourceUpdate e o ResourcePatch que escrevem um ServiceUser não aceitam esse campo). Defini-lo como true faz com que essa escrita gere um evento de alteração, de modo que ações subsequentes como Webhook são executadas. O padrão é não gerar (uma escrita silenciosa do sistema).
ResourceCreate
Cria um Content ou Media. Content e Media compartilham o modelo fields, e os valores são mapas de locale.
| Campo | Aplica-se a | Descrição |
|---|---|---|
resource | Comum | "Content" ou "Media" (obrigatório) |
contentType | Content | O Content Type a criar ({ sys: { id } }). Obrigatório para Content |
fields | Comum | Mapa de campos { "<field>": { "<locale>": valor } }. Cada campo preenchido exige o bucket do locale padrão. As chaves de Content seguem a definição do Content Type; as chaves de Media são fixas (title, description, file) |
locale | Comum | (Conveniência) Se fornecido, cada valor em fields é automaticamente encapsulado como { <locale>: valor } |
publish | Comum | Publica após a escrita (exposto na CDA/ACDA). Padrão true |
Mediafile: o valor defields.file.{locale}é uma instrução de ingestão{ "source": <expressão de valor>, "encoding": "url"|"base64", "transformations": [ … ] }.sourceeencodingsão obrigatórios, etransformationssó é escrito quando você quer alterar o arquivo recebido antes de salvá-lo (veja Transformação de arquivos). Em uma escrita que inclui um arquivo, o motor realiza a ingestão (se forurl, baixa; se forbase64, decodifica, e então faz o upload e o processa). Essa ingestão não tem tempo declarado, portanto sai do orçamento base de 30 segundos (veja Orçamento de tempo) e não é contada no limite de chamadas externas. Você também pode criar um Media sem arquivo (fileless). Sepublish:truemas não houver arquivo ou o processamento estiver incompleto, a etapa de publicação gera erro; sepublish:false, permaneceDraft.- Resultado (vínculo de
name): o recurso criado.{ /<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 é uma instrução de ingestão
{ "type": "ResourceCreate", "resource": "Media",
"fields": {
"title": { "en-US": "{ /payload/fields/prompt }" },
"file": { "en-US": { "source": "{ /gen/body/data/0/url }", "encoding": "url" } }
}, "name": "img" }Transformação de arquivos (transformations)
Ao vender um e-book, você pode querer entregar uma cópia com o e-mail de quem comprou estampado em todas as páginas do PDF. Ao escrever transformations na instrução de ingestão, você pode alterar o arquivo recebido antes de salvá-lo. A única transformação suportada é PdfWatermark.
| Campo da instrução de ingestão | Obrigatório | Descrição |
|---|---|---|
transformations | Opcional | Um array de objetos de transformação. Se for omitido ou estiver vazio, o arquivo recebido é colocado no Media sem alterações. Se não for um array, o salvamento do Script é rejeitado |
- As transformações são aplicadas na ordem do array. O resultado de uma transformação é a entrada da seguinte, e o último resultado se torna o arquivo do Media.
- Pode ser escrito em qualquer instrução de ingestão de
ResourceCreate,ResourceUpdate,ResourcePatch. - O original apontado por
sourcenão é alterado. Para estampar um texto em um arquivo que já está no Space, leia esse Media comResourceReade coloque{ /<name>/fields/file/<locale>/url }emsourcepara criar um novo Media. - A transformação também faz parte da ingestão. O tempo que ela leva sai do orçamento base de 30 segundos (veja Orçamento de tempo).
PdfWatermark: estampar um texto em todas as páginas do PDF
| Campo | Obrigatório | Descrição |
|---|---|---|
type | Obrigatório | "PdfWatermark". Com qualquer outro valor, o salvamento do Script é rejeitado |
text | Obrigatório | O texto a estampar. É uma string e pode misturar referências { /pointer }. Não aceita objetos de operação JsonLogic. Precisa ser uma única linha, de 1 a 64 caracteres, composta de caracteres permitidos |
color | Opcional | A cor do texto. É escrita como #RRGGBB (seis dígitos hexadecimais) e, se omitida, é #808080 (cinza). Aceita apenas um literal, e não uma expressão de valor; se o formato for diferente, o salvamento do Script é rejeitado |
- Os campos que não estão na tabela são ignorados. Não é possível especificar o tamanho da fonte, a posição nem a opacidade.
- O texto é estampado em todas as páginas. Uma linha de texto é colocada ao longo da diagonal que passa pelo centro da página (do canto inferior esquerdo ao canto superior direito).
- O texto é sobreposto ao conteúdo com 30% de opacidade. O conteúdo original sob o texto continua legível.
- O tamanho da fonte é definido automaticamente em cada página. O texto é estampado em um tamanho que ocupa 80% do comprimento da diagonal, sem ultrapassar 15% do lado mais curto da página. Textos curtos saem grandes, textos longos saem pequenos, e o texto nunca ultrapassa os limites da página.
- A fonte é a Helvetica, uma fonte padrão do PDF.
- Se o arquivo é um PDF ou não é determinado pelo conteúdo, e não pelo nome ou pela extensão do arquivo.
// parte do array statements. Um novo Media com o e-mail do comprador e o número do pedido estampados no PDF do e-book comprado
{ "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" }Como é uma cópia para um único comprador, publish foi definido como false, para que ela não seja entregue pela CDA/ACDA. Se for omitido, a cópia é publicada conforme o padrão true.
Limites do texto do PdfWatermark
Em text, só podem ser usados os caracteres que a fonte padrão do PDF consegue desenhar (o conjunto de caracteres WinAnsi).
| Tipo | Caracteres |
|---|---|
| Caracteres ASCII imprimíveis | Espaço, letras ASCII maiúsculas e minúsculas, dígitos e símbolos básicos como @ . # { } |
| Caracteres Latin-1 | Letras e símbolos da Europa Ocidental, como é ü ñ ß · © °. Exceto o espaço não separável (U+00A0) e o hífen opcional (U+00AD) |
| Símbolos adicionais | € ‚ ƒ „ … † ‡ ˆ ‰ Š ‹ Œ Ž ‘ ’ “ ” • ˜ ™ š › œ ž Ÿ, além dos dois tipos de traço (U+2013, U+2014) |
Nenhum outro caractere pode ser usado. Isso inclui os caracteres latinos estendidos que não estão na tabela (como ł, ő, ș), os caracteres não latinos, os emojis e também a quebra de linha e a tabulação.
Ao salvar o Script, o text é verificado tal como foi escrito. Em um text com { /pointer }, o valor resolvido só é conhecido no momento da execução, por isso esse valor é verificado novamente durante a execução. Os códigos dos erros que ocorrem ao salvar estão em Erros.
| Limite | text literal | text com { /pointer } |
|---|---|---|
| Uma única linha de caracteres permitidos | Salvamento do Script rejeitado | A string escrita inteira (incluindo os caminhos dos ponteiros) é verificada ao salvar, e o salvamento do Script é rejeitado. O valor resolvido é verificado novamente na execução, que falha com status 400 |
| 1 a 64 caracteres | Salvamento do Script rejeitado | Não é verificado ao salvar. Na execução, é verificado pelo comprimento do valor resolvido, e a execução falha com status 400 |
- Calcule o comprimento do texto fixo. Em
"Purchased by { /payload/fields/email } · Order { /payload/fields/orderId }", o texto fixo fora do template tem 22 caracteres, portanto sobram 42 caracteres, somados, para o e-mail e o número do pedido. Se chegar um valor real mais longo que isso, o Script é salvo, mas aquela execução falha. - Estampe identificadores, e não nomes. Um nome de comprador que contenha caracteres fora da tabela faz a execução falhar. Um e-mail ou um número de pedido em ASCII pode ser estampado.
Quando a transformação de arquivos falha
Nos casos abaixo, esse Statement falha. Como qualquer outra falha em tempo de execução, pode ser tratada com Try/catch. A transformação ocorre antes da escrita do Media, portanto, em caso de falha, nenhum Media novo é criado e o destino de ResourceUpdate, ResourcePatch também não é alterado.
| Situação | Resultado |
|---|---|
O arquivo de source não é um PDF | Falha com status 422 |
| Não é possível ler o PDF, ou ele está criptografado (incluindo PDFs que não pedem senha para abrir, mas apenas restringem permissões) | Falha com status 422 |
O text resolvido está vazio ou contém apenas espaços em branco | Falha com status 400 |
O text resolvido está fora da faixa de 1 a 64 caracteres ou contém caracteres não permitidos | Falha com status 400 |
ResourceUpdate
Substitui totalmente os campos do Content, Media ou ServiceUser de destino (PUT). O que você passa em fields se torna o novo conjunto de campos, e todo campo e locale que não estiver presente aqui é removido. Para alterar apenas parte dele, use ResourcePatch.
| Campo | Descrição |
|---|---|
resource | "Content", "Media", "ServiceUser". Os campos que podem ser escritos em um ServiceUser e as restrições estão em Trabalhar com o diretório de membros |
target | O destino ({ sys: { id } }, obrigatório). O id geralmente é { /ptr } |
fields | O conjunto completo de campos a escrever. Os valores são mapas de locale. Por ser uma substituição total, todo campo e locale que não estiver presente aqui é removido. O file de Media é uma instrução de ingestão (veja ResourceCreate acima). Os arquivos listados são sempre reingeridos, e os arquivos de locales não fornecidos são excluídos |
locale | (Conveniência) Encapsula fields automaticamente |
version | (Opcional) Uma expressão de valor (Int). Bloqueio otimista. Se fornecido, a atualização é executada somente se corresponder ao sys.version atual do destino; em caso de divergência, ela é abortada com um erro de conflito de versão (que pode ser capturado com Try). Se omitido, não há verificação (last-write-wins). ServiceUser não o aceita |
publish | Republica após a atualização. Padrão true |
Se você usar o Update apenas para alterar os metadados de um Media, o file fica de fora e todos os arquivos são excluídos (por ser uma substituição total). Para alterações parciais, use obrigatoriamente ResourcePatch.
{ "type": "ResourceUpdate", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "title": { "en-US": "Hello", "ko-KR": "안녕" }, "status": { "en-US": "published" } } }ResourcePatch
Mescla parcialmente os campos do Content, Media ou ServiceUser de destino (PATCH). Ela sobrescreve apenas os campos (e os locales dentro deles) que você passa em fields e mantém intactos os campos e locales que você não menciona. O formato dos valores, locale, version e publish são os mesmos de ResourceUpdate.
| Campo | Descrição |
|---|---|
resource | "Content", "Media", "ServiceUser". Os campos que podem ser escritos em um ServiceUser e as restrições estão em Trabalhar com o diretório de membros |
target | O destino ({ sys: { id } }, obrigatório). O id geralmente é { /ptr } |
fields | Os campos a sobrescrever. Os valores são mapas de locale. Atualiza apenas os campos e buckets de locale especificados (o restante é mantido). Se um valor for um null literal, esse par (campo, locale) é excluído. O file de Media é uma instrução de ingestão (veja ResourceCreate acima) |
locale | (Conveniência) Encapsula fields automaticamente |
version | (Opcional) O mesmo que ResourceUpdate (bloqueio otimista). ServiceUser não o aceita |
publish | Republica após a atualização. Padrão true |
- Excluir um locale ou arquivo específico: forneça um
nullliteral como valor. Por exemplo:"title": { "fr-FR": null }(exclui o título fr-FR),"file": { "en-US": null }(exclui o arquivo en-US). Uma expressão de valor que é avaliada como null em tempo de execução não é uma exclusão, mas um erro (apenas um null literal exclui). - Se você fornecer uma instrução de ingestão ao
filede Media, ela substitui o arquivo daquele locale. Se você não fornecer um arquivo, ele é mantido.
// +1 apenas em viewCount(en-US). title, outros locales e todo o resto são mantidos como estão
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "viewCount": { "en-US": { "$+": [ "{ /payload/fields/viewCount }", 1 ] } } } }ResourceDelete
Exclui o destino. Apenas os status Draft e Archived podem ser excluídos. Se estiver Published ou Changed, a exclusão é rejeitada, portanto você deve fazer ResourceUnpublish primeiro. Para o Media, a exclusão é rejeitada enquanto o arquivo está sendo processado. Não faz auto-unpublish (igual à CMA/ACMA).
| Campo | Descrição |
|---|---|
resource | "Content" ou "Media" |
target | O destino ({ sys: { id } }, obrigatório) |
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }ResourcePublish, ResourceUnpublish, ResourceArchive, ResourceUnarchive
Controlam de forma independente o estado de publicação e de arquivamento do destino. Os quatro compartilham os mesmos campos. A pré-condição de status de cada operação é a mesma da CMA/ACMA. O ResourcePublish não pode ser feito a partir de Archived e exige que o processamento do arquivo esteja concluído. O ResourceUnpublish só pode ser feito a partir de Published, Changed, o ResourceArchive só a partir de Draft, e o ResourceUnarchive só a partir de Archived.
| Campo | Descrição |
|---|---|
resource | "Content" ou "Media" |
target | O destino ({ sys: { id } }, obrigatório) |
version | (Opcional) Uma expressão de valor (Int). Bloqueio otimista. Se fornecido, a operação é executada apenas se corresponder ao sys.version atual |
{ "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 }" } } }Leituras de recurso
ResourceRead e ResourceFind leem o recurso e o vinculam como valor, e ResourceCount apenas conta. Nenhum dos três altera o estado (sem propagateEvents). O ResourceForEach também é uma leitura na própria consulta, mas, se você colocar em onEach um Statement de escrita de recurso, essa escrita é executada em cada item e altera o estado.
Os quatro Statements (ResourceRead, ResourceFind, ResourceForEach, ResourceCount) usam from (padrão Current) para definir de qual versão armazenada ler. Current é o rascunho mais recente visto no estúdio de conteúdo (o valor que a CMA/ACMA lê); Published é o snapshot de publicação (o valor no momento da última publicação, entregue pela CDA/ACDA). (ServiceUser não é publicado, portanto aceita apenas Current; veja Trabalhar com o diretório de membros.)
Além disso, ResourceFind, ResourceForEach, ResourceCount ativam e desativam a Busca avançada (Advanced Search) por meio de advanced (padrão true). Se você não a declarar, ela vem ativada. É exclusiva de Content, portanto é ignorada nas leituras de Media e de ServiceUser. Quando ativada, em where é possível usar os operadores regex, near e within e a busca de texto completo (em um campo LongText com a busca de texto completo ativada, eq também encontra os itens que contêm o valor, por correspondência parcial ou aproximada), e order pode ordenar por fields.*. Quando desativada, esses três operadores são rejeitados, eq em texto é correspondência exata, e prefix e os operadores de comparação e de lista funcionam independentemente da busca avançada. Um item recém-criado ou editado leva um breve instante (cerca de 1 segundo) para ser refletido na busca avançada, portanto pode não ser capturado pela consulta de busca avançada imediatamente seguinte. Como ela vem ativada por padrão, esse atraso vale para toda consulta, a menos que você deixe advanced como false. Para ler de imediato um item recém-escrito, use ResourceRead por id (o armazenamento primário, sem esse atraso) ou consulte pelo sys.id que a escrita retornou.
O createdBy: ":self" de where significa "apenas o que o usuário que está chamando agora criou". Porém, ele não pode ser usado em um Script que permite chamada anônima (anonymousCallEnabled). Nesse caso, :self é resolvido para o autor, e não para o chamador, de modo que os recursos do autor seriam silenciosamente abertos; por isso o salvamento de uma definição assim é rejeitado (veja Chamada anônima).
Em where e order, os campos de conteúdo são escritos como fields.<field> (o nome sozinho não é reconhecido). Para fields.<field>, aplica-se automaticamente o locale padrão do Space, portanto você não anexa um locale diretamente. Os fields.status e fields.slug nos exemplos abaixo já são consultas no locale padrão. Somente para consultar apenas um locale específico (não padrão), você o indica explicitamente como fields.<field>.<locale> (por exemplo, fields.title.ko-KR). sys.* (como sys.createdAt) e createdBy (:self) são escritos tal como estão, sem fields.. As regras detalhadas estão em Expressões de valor: o locale em where e order.
Trabalhar com o diretório de membros (ServiceUser)
ResourceRead, ResourceFind, ResourceForEach aceitam "ServiceUser" em resource para ler o diretório de membros daquele Space (o ResourceCount não o aceita; veja ResourceCount abaixo). Use-os em fluxos como conferir quem é o dono de um pedido, ou encontrar um membro pelo e-mail e passar o sys.id dele ao Statement seguinte. As regras abaixo valem para os cinco Statements que trabalham com o diretório de membros.
- São apenas dois os campos que podem ser alterados.
ResourceUpdateeResourcePatchaceitam"ServiceUser"e alteramroleOverrideeenableLogin. NoResourcePatch, passarnullemroleOverrideremove o papel que estava definido.ResourceCreate,ResourceDeletee os Statements de publicação e arquivamento não aceitam"ServiceUser", e uma definição assim é rejeitada no momento do salvamento. Não é algo que se libere acrescentando permissões: o Script não pode criar nem excluir um membro, portanto a recusa não vem de um erro de permissão, mas de um Statement escrito de forma inválida. - O membro não tem
versionnempropagateEvents. Escrever qualquer um dos dois faz a execução falhar. Como oResourceUpdatesubstitui todos os campos que podem ser escritos, oenableLoginé obrigatório nele, e não informarroleOverrideremove o papel que estava definido. Para alterar apenas um dos dois, use oResourcePatch; nesse caso, é preciso informar ao menos um dos dois campos. - O autor precisa ter permissão sobre o diretório de membros para que a definição seja salva. A verificação não usa o mapa de permissões, como em Content e Media, mas confere se o
settingsdo SpaceRole do autor temSETTING_SERVICE_LOGIN(ouSETTING_ALL). Isso porque o diretório de membros é, em todos os outros caminhos também, um recurso governado pelas configurações do Space. Se não tiver, o salvamento é rejeitado (veja Modelo de segurança). fromaceita apenasCurrent. Como o membro não é um recurso publicado, passarPublishedfaz a execução falhar.contentTypeeadvancedsão ignorados. O diretório de membros não se divide por Content Type (é um único conjunto para todo o Space), e a busca avançada também é exclusiva de Content.- O
sys.emaildewhereaceita apenas operadores da família de igualdade exata (eq,ne,in,nin). Como o endereço do membro é armazenado criptografado, comparações de ordem eprefixnão têm sentido. Se você passar outro operador, em vez de devolver silenciosamente 0 resultado, a execução falha. - O resultado é o próprio recurso ServiceUser. Referencie-o como
{ /<name>/sys/id }ou{ /<name>/nickname }. A estrutura é abordada na referência de ServiceUser. Para enviar um e-mail ao membro encontrado, não extraia o endereço: passe osys.iddele aotoServiceUserdeEmailSend(o motor resolve o endereço imediatamente antes do envio, de modo que o endereço do membro não entra no espaço de variáveis do Script).
// encontra um membro pelo e-mail. null se não houver
{ "type": "ResourceFind", "resource": "ServiceUser",
"where": { "sys.email": { "eq": "{ /payload/fields/email }" } }, "name": "member" }ResourceRead
Lê um único item por id (get-by-id). O resultado vincula o recurso inteiro ao nome.
| Campo | Descrição |
|---|---|
resource | "Content", "Media", "ServiceUser" |
target | O destino ({ sys: { id } }). O id é uma expressão de valor |
from | (Opcional) Current (padrão, o rascunho mais recente) ou Published (o snapshot de publicação). Para ServiceUser, apenas Current |
- Resultado: o que é vinculado é o próprio recurso. Se você deu um
namea este Statement, referencie-o diretamente como{ /<name>/sys/id }e{ /<name>/fields/<field>/<locale> }(com o"name": "order"do exemplo abaixo,{ /order/sys/id }). Não é uma lista, então nenhum índice de array entra em jogo. - Se o destino não existir, gera erro. Você pode envolvê-lo em
Trypara tratar isso.
{ "type": "ResourceRead", "resource": "Content",
"target": { "sys": { "id": "{ /payload/fields/orderId }" } }, "name": "order" }ResourceFind
Lê o primeiro item único correspondente por filtro. Se não houver nenhum, é null. Use-o para encontrar um registro por uma chave de negócio única (slug, email, sku).
| Campo | Descrição |
|---|---|
resource | "Content", "Media", "ServiceUser" |
contentType | O Content Type no qual pesquisar ({ sys: { id } }). Obrigatório quando é Content. Ignorado em Media e ServiceUser |
where | O filtro ({ "<field>": { "<op>": <valor> } }). Os operadores são os da lista de operadores (regex/near/within exigem advanced). createdBy: ":self" suportado. Para o sys.email de ServiceUser, apenas eq, ne, in, nin (Trabalhar com o diretório de membros) |
order | A ordenação que determina o "primeiro" quando há várias correspondências (ex.: "-sys.createdAt") |
from | (Opcional) Current (padrão, o rascunho mais recente) ou Published (o snapshot de publicação). Para ServiceUser, apenas Current |
advanced | (Opcional) Executar via busca avançada (Advanced Search). Somente Content (Media e ServiceUser são ignorados). Padrão true. Veja a nota Leituras de recurso acima |
- Resultado: vincula o primeiro recurso correspondente ao
namedeste Statement. Referencie-o diretamente como{ /<name>/fields/<field>/<locale> }. Como énullquando não há nenhum, ramifique conforme a existência com{ "==": [ "{ /<name> }", null ] }(o padrão típico find-then-upsert).
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }, "name": "found" }ResourceForEach
Percorre internamente os recursos que atendem ao filtro e executa onEach em cada item. É um Statement para realizar uma operação em cada item, sem construir uma coleção para usar como valor. Use-o em trabalhos repetitivos como publicar rascunhos em lote, editar em lote os Content que atendem a uma condição, ou enviar/sincronizar cada item para fora. Para ler um único registro, use ResourceRead (id) ou ResourceFind (filtro).
| Campo | Descrição |
|---|---|
resource | "Content", "Media", "ServiceUser" (obrigatório) |
contentType | O Content Type do escopo do percurso ({ sys: { id } }). Obrigatório quando é Content. Ignorado em Media e ServiceUser |
where | O filtro ({ "<field>": { "<op>": <valor> } }). O significado é o mesmo do where de ResourceFind (a restrição do sys.email de ServiceUser também é idêntica). Os operadores são os da lista de operadores (regex/near/within exigem advanced). createdBy: ":self" suportado |
order | A ordenação (ex.: "sys.createdAt,sys.id"). Se ausente, a ordem padrão da plataforma |
from | Current (padrão, o rascunho mais recente) ou Published (o snapshot de publicação). Para ServiceUser, apenas Current |
advanced | Percorre via busca avançada (Advanced Search). Somente Content (Media e ServiceUser ignorados). Padrão true. Veja a nota Leituras de recurso acima |
limit | (Opcional, 1 ou mais) O limite máximo do total de itens processados (não é o tamanho da página). Se ausente, percorre até o limite máximo da plataforma (10.000 itens) |
name | (Opcional) O nome ao qual vincular o item atual. É revinculado a cada iteração e referenciado dentro de onEach como { /<name> } (mesma vida útil do name de Loop; permanece vinculado ao último item mesmo após o fim do percurso). Omita se você não referenciar o item |
onEach | O array de Statements filhos a executar em cada item (obrigatório) |
- Não vincula uma coleção (
foreach, nãomap). Não há{ items, next }nem cursor. Você não recebe o resultado do percurso como um valor; em vez disso, executaonEachem cada item. Se precisar de uma lista, colete você mesmo comSetVar. Se precisar apenas da contagem, use oResourceCount. - Mesmo sem
limit, não é um percurso infinito. Sem ele, percorre até o limite máximo da plataforma (10.000 itens) e, se atingir esse limite ainda havendo correspondências, falha (para não reportar sucesso deixando itens intocados). Por outro lado, atingir olimitdeclarado é uma parada intencional, portanto um encerramento normal. Umlimitque ultrapasse o limite máximo é recusado ao salvar. - Não há cursor. Se concluir tudo, é sucesso; se for interrompido no meio (estouro de relógio de parede ou de cota, uma falha não tratada em
onEach), é falha, e o erro aponta em qual item e por que falhou. A retomada é expressa pelo autor com os próprios dados (deixandowherecomo "não processado" e marcando a conclusão ao fim deonEach, uma reexecução continua a partir do que restou). - No orçamento de tempo, ele entra como multiplicação. O tempo que este Statement declara é o tempo declarado por
onEachmultiplicado pela quantidade de itens processados (limit; 10.000 se não houver) (veja Orçamento de tempo). Por ser um Statement composto que possui filhos, ele próprio não conta no orçamento de chamadas externas leaf; são os Statements de chamada externa dentro deonEachque entram no orçamento. - Em
onEach, como em qualquer outro Statement, você pode incluir chamadas externas (Http,EmailSend) ou ingestão de arquivo de Media (igual aobodydeLoop). A razão de existir deste Statement é processar o resultado de uma consulta de recurso uma vez por item.
// encontra todos os posts em rascunho e publica cada item
{ "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
Conta apenas a quantidade de itens que atendem ao filtro. Como não traz os itens, use-o quando você precisa do número, e não da lista. É o lugar para conferir o estoque restante, julgar se já existe um registro com o mesmo valor, ou verificar se um limite foi ultrapassado.
| Campo | Descrição |
|---|---|
resource | "Content" ou "ContentType" (obrigatório). Media e ServiceUser não podem ser contados, e escrevê-los assim faz o salvamento ser rejeitado |
contentType | O Content Type do escopo da contagem ({ sys: { id } }). Obrigatório quando é Content. Ao contar Content Type, é ignorado (o Space inteiro é um único conjunto) |
where | O filtro. O significado é o mesmo do where de ResourceFind. Conta todos os itens correspondentes |
from | (Opcional) Current (padrão, o rascunho mais recente) ou Published (o snapshot de publicação) |
advanced | (Opcional) Executar via busca avançada (Advanced Search). Somente Content (ignorado ao contar Content Type). Padrão true. Veja a nota Leituras de recurso acima |
name | (Opcional) O nome ao qual vincular a contagem |
- Resultado: vincula a quantidade de correspondências ao
namedeste Statement. Referencie-a como{ /<name> }para usá-la em comparações e ramificações. - Ele não devolve os itens. Se você precisa dos itens, use
ResourceFind(o primeiro item único correspondente) ouResourceForEach(executa em cada item). - Não percorra com
ResourceForEachsó para obter uma contagem. O percurso reserva o orçamento de tempo multiplicado pela quantidade de itens (veja Orçamento de tempo) e falha se atingir o limite máximo da plataforma ainda havendo correspondências. Se você só precisa contar, este Statement resolve de uma vez. - Não existem
ordernemlimit. Para contar não é necessária uma ordem, e tudo o que corresponde é contado.
// conta quantos comentários este post tem
{ "type": "ResourceCount", "resource": "Content", "contentType": { "sys": { "id": "ct_comment" } },
"where": { "fields.postId": { "eq": "{ /payload/sys/id }" } }, "name": "commentCount" }Externo
Http
Chama um endpoint HTTP externo. Por ser uma chamada externa, ele é contado no limite de chamadas externas por plano. Ao orçamento de tempo soma-se o timeoutMs e, se você enviar arquivos, soma-se também o tempo de download de cada arquivo. Se você especificar retry, essa soma é multiplicada pelo número de tentativas (1 + retry).
| Campo | Descrição |
|---|---|
method | "GET", "POST", "PUT", "PATCH", "DELETE" |
url | A URL de destino (uma expressão de valor; { /ptr } pode ser interpolado) |
headers | [{ "key", "value" }]. value é uma expressão de valor. Valores secretos, como chaves de API, não são escritos literalmente: declare-os em secrets e leia-os com { /secrets/<key> } (ex.: "Bearer { /secrets/LLM_API_KEY }"). Se você colocar Content-Type aqui, o body é serializado nesse formato (abaixo) |
body | O corpo da requisição (uma expressão de valor ou JSON). O formato em que ele é enviado é definido pelo cabeçalho Content-Type |
urlBody | A URL do arquivo a enviar como corpo da requisição (expressão de valor). A plataforma baixa o arquivo dessa URL e envia os bytes como estão (Enviar um arquivo como corpo) |
multipartBody | O array de parts a enviar como multipart/form-data (1 ou mais). É possível misturar parts de texto e parts de arquivo (Enviar um arquivo como corpo) |
timeoutMs | O tempo limite desta chamada (ms) |
retry | O número de novas tentativas quando o status da resposta é 400 ou superior. Padrão 0; limite máximo 2 |
ignoreStatusCode | Se esta chamada deve ser tratada como uma falha quando o status final (após as novas tentativas) é 400 ou superior. Quando false (padrão), ela é tratada como uma falha e se torna um alvo de Try/catch. Quando true, ela não é tratada como uma falha e { status, body } é vinculado como está (o chamador ramifica com base no próprio status) |
responseType | Como o corpo da resposta é recebido. "Json" (padrão) faz o parse dele em objeto ou array; "Text" recebe como string |
- Use apenas um entre
body,urlBodyemultipartBody. Se você escrever dois ou mais, o salvamento é rejeitado. Se não houver nenhum dos três, a requisição é enviada sem corpo. - Resultado:
{ status, body }. Se você deu umnamea este Statement,{ /<name>/status },{ /<name>/body/... }. A forma debodyé definida porresponseType. responseTypese aplica apenas a uma resposta bem-sucedida. O corpo de uma resposta com status 400 ou superior é vinculado para diagnóstico independentemente do valor declarado (o valor parseado quando é JSON, uma string caso contrário).- Se for
"Json"e o corpo não for JSON, esta chamada falha (alvo deTry/catch). Para APIs que não devolvem JSON, receba o corpo como"Text"e faça o parse comParseJsonquando precisar tratá-lo como valor. "Text"é decodificado com o charset doContent-Typeda resposta e tratado como UTF-8 quando não há charset. Se o corpo estiver vazio,bodyénullnos dois casos.- Limite de tamanho da resposta: O corpo da resposta tem no máximo 10MiB. Se exceder isso, esta chamada falha com uma exceção e pode ser tratada como qualquer outra falha em tempo de execução com
Try/catch(é uma falha baseada em tamanho, portanto não é suprimida porignoreStatusCode).
{ "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" }Em que formato o body é enviado
O Content-Type que você coloca em headers define o formato de serialização do body. A comparação ignora maiúsculas e minúsculas e parâmetros como ;charset=…, olhando apenas a parte inicial. Quando o cabeçalho está ausente ou o valor está vazio, a requisição é enviada como application/json. Esse cabeçalho só é anexado quando existe um body, portanto, se não houver body, o cabeçalho que você escreveu é enviado como está. Se você colocar a mesma chave várias vezes, apenas o primeiro valor é usado e elas são unificadas em uma só.
Um body que não cabe no formato declarado é enviado com uma correção para um formato em que ele caiba. O cabeçalho nunca diz algo diferente do corpo que é realmente enviado.
Estas são as combinações que são enviadas exatamente como declaradas.
Content-Type declarado | Forma do body | body enviado |
|---|---|---|
application/json | Qualquer uma | JSON |
application/x-www-form-urlencoded | Objeto ou array | order[id]=A-2481&order[amount]=34000 |
text/plain | Escalar | O valor como está |
Outros (text/xml etc.) | Qualquer uma | JSON |
Estas são as combinações que são corrigidas porque não cabem no formato declarado.
Content-Type declarado | Forma do body | Content-Type realmente enviado | body enviado |
|---|---|---|---|
application/x-www-form-urlencoded | Escalar | text/plain;charset=UTF-8 | O valor como está |
text/plain | Objeto ou array | application/json | JSON |
Estas duas linhas esclarecem como a requisição é enviada quando o par está desalinhado, e não são uma forma de obter o formato pretendido. Quando o body é montado por uma expressão de valor, ele pode resultar em um escalar conforme o payload do momento da execução, e nesse caso a correção acontece sem erro. Se o lado que recebe questionar o formato, ajuste a forma do body ou o Content-Type de acordo com a sua intenção.
O form-urlencoded expande objetos em chaves com colchetes e arrays em índices.
body | Chaves e valores expandidos |
|---|---|
{ "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= |
As chaves e os valores são enviados com codificação percentual em UTF-8. A tabela acima está na forma decodificada para mostrar a estrutura das chaves. Mesmo que um valor contenha & ou +, ele é transmitido como está, sem ser confundido com um separador de pares ou um espaço.
A notação que expande o aninhamento em chaves com colchetes é uma convenção amplamente usada, e não uma especificação do próprio formato. Verifique se o lado que recebe restaura order[id] como um objeto aninhado e, se não restaurar, monte o body com chaves planas.
{ "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" }Enviar um arquivo como corpo
Às vezes é preciso repassar a uma API externa um arquivo que você enviou para um Media. Os bytes de um arquivo não cabem em uma expressão de valor. Em vez disso, se você especificar a URL onde está o arquivo, a plataforma baixa o arquivo dessa URL e o envia como corpo da requisição. Para enviar um único arquivo como o corpo inteiro, use urlBody; para enviar arquivos e campos de texto juntos como formulário, use multipartBody.
urlBody: enviar um único arquivo como o corpo inteiro
O arquivo baixado da URL especificada é enviado como corpo da requisição tal como está, sem conversão. É adequado para APIs que recebem o arquivo por PUT em uma URL de upload. Abaixo está um exemplo que lê o PDF de um contrato (Media) e o envia para a URL de upload de um serviço de assinatura eletrônica.
// parte do array statements
{ "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" }O Content-Type da requisição é definido na ordem a seguir.
- Se você especificou
Content-Typeemheaders, esse valor é usado como está. A correção da serialização do body não se aplica. - Se não especificou ou o valor estiver vazio, é usado o
Content-Typecom que a URL do arquivo respondeu. - Se ele também não existir, é usado
application/octet-stream.
multipartBody: enviar arquivos e texto como formulário
A requisição é enviada como multipart/form-data. Cada elemento do array vira uma part, e as parts são enviadas na ordem do array. O array precisa ter 1 ou mais elementos; um array vazio faz o salvamento ser rejeitado. O Content-Type é sempre multipart/form-data (a string de delimitação é gerada automaticamente), e o Content-Type escrito em headers é ignorado.
| Campo da part | Descrição |
|---|---|
name | O nome do campo do formulário (obrigatório). Não é uma expressão de valor, mas um literal. Se você usar o mesmo nome várias vezes, cada um é enviado como uma part separada |
value | O conteúdo de uma part de texto (expressão de valor). Se o resultado for vazio, é enviado como valor vazio |
url | A URL de onde baixar os bytes de uma part de arquivo (expressão de valor). Ex.: a url de um arquivo de Media |
fileName | O nome do arquivo colocado no Content-Disposition da part (expressão de valor). Pode ser usado apenas em parts com url |
contentType | O Content-Type da part (expressão de valor) |
- Escreva exatamente um entre
valueeurl. Se você escrever os dois ou nenhum, o salvamento é rejeitado. EscreverfileNameem uma part comvaluetambém faz o salvamento ser rejeitado. - Se você omitir
fileName, é usado o último segmento do caminho daurl, decodificado (sem a query string). Se esse segmento estiver vazio ou contiver caracteres de controle, o nome enviado éfile. - Se você omitir
contentType, uma part comvalueé enviada semContent-Type, e uma part comurlusa oContent-Typecom que a URL do arquivo respondeu (application/octet-streamse não houver). Se você especificar um valor que não está no formato de media type, esseHttpfalha com status 400 na execução. - Se o resultado da expressão de valor de
fileNameoucontentTypecontiver um caractere de quebra de linha, esseHttpfalha com status 400 antes de enviar a requisição.
Abaixo está um exemplo que envia uma foto de produto (Media) junto com opções de análise para um serviço de análise de imagens. Como a part image não tem fileName, o último segmento da URL do arquivo de Media (ex.: tumbler.png) é enviado como nome do arquivo.
// parte do array statements
{ "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" }As falhas de um Http que envia arquivos também se dividem em dois tipos. O estouro de tempo é tratado como uma resposta com status 504, portanto retry e ignoreStatusCode se aplicam. Às falhas relacionadas a arquivos da tabela abaixo, nenhum dos dois se aplica. A classificação completa das falhas de Http está em As duas formas de falha do Http.
Com ignoreStatusCode: true, quando o tempo se esgota, { "status": 504, "body": "timed out after …ms" } é vinculado como resultado.
Uma requisição precisa terminar dentro de timeoutMs, e o tempo de espera pela resposta da URL do arquivo também está incluído nele. Ao enviar um arquivo, o tempo limite aumenta no tempo necessário para enviar esse arquivo à velocidade de 10.000.000 bytes por segundo. O tempo acrescido não ultrapassa o tempo de download do plano multiplicado pelo número de arquivos. Porém, se a URL do arquivo não informar o tamanho do arquivo, o tempo aumenta apenas na medida do que foi efetivamente enviado, portanto, se a transferência for lenta ou parar no meio, o tempo pode se esgotar mais cedo.
Nos casos abaixo, esse Http falha imediatamente. Não há nova tentativa com retry, e a chamada é tratada como falha mesmo com ignoreStatusCode: true. Como qualquer outra falha em tempo de execução, pode ser tratada com Try/catch.
| Situação | Resultado |
|---|---|
| O arquivo a baixar é maior que o limite de download do plano | Falha com status 413. Se a URL do arquivo informar o tamanho de antemão, falha antes do download; se não informar, falha no momento em que o limite é ultrapassado durante o download |
| A URL do arquivo respondeu com um status que não é 2xx (incluindo redirecionamentos), ou não foi possível conectar-se à URL do arquivo | Falha com status 502 |
O contentType de uma part não está no formato de media type | Falha com status 400 |
O valor de fileName, contentType de uma part contém um caractere de quebra de linha | Falha com status 400. Falha antes de enviar a requisição |
- O limite de download se aplica por URL de arquivo. Se
multipartBodytiver várias parts de arquivo, o limite se aplica separadamente a cada arquivo. - Os bytes enviados, incluindo os arquivos, são somados ao item "Chamadas externas" da transferência de dados do plano.
EmailSend
Envia 1 e-mail por meio de um EmailAccount registrado. Os campos aceitos são apenas os que mapeiam diretamente para SMTP/MIME. Não há id de template, envio agendado nem extensões por provedor (se você precisar de recursos assim, chame diretamente a API do serviço de e-mail correspondente com Http). O remetente (o endereço que envia) não é definido aqui, mas vem do EmailAccount apontado por account.
| Campo | Descrição |
|---|---|
account | Referência ao EmailAccount por onde enviar ({ sys: { id } }, obrigatório). Geralmente um id literal. Se fornecido como expressão de valor, é resolvido no momento do envio, portanto não pode ser verificado ao salvar |
to | Endereço do destinatário (expressão de valor). Use exatamente um entre to e toServiceUser |
toServiceUser | Especifica o destinatário como referência a um ServiceUser ({ sys: { id } }; esse sys.id pode ser uma expressão de valor). O motor resolve o endereço imediatamente antes do envio, de modo que o endereço do membro não entra no espaço de variáveis do Script |
cc | Array de endereços em cópia (expressão de valor) |
bcc | Array de endereços em cópia oculta (expressão de valor) |
subject | O assunto (expressão de valor, obrigatório) |
body | O corpo (expressão de valor). Sempre enviado como text/html, portanto escreva markup, não texto puro (quebras de linha viram espaço, < é interpretado como tag). O resultado das expressões de valor interpoladas passa por escape de HTML, a menos que autoEscape seja false. Use exatamente um entre body e templateBody |
templateBody | Monta o corpo a partir de um modelo. Tem o formato { source, data } e você usa exatamente um entre ele e body. Veja Montar o corpo com um modelo abaixo |
autoEscape | (Opcional, padrão true) Define se os valores interpolados no corpo passam por escape de HTML. Os alvos são o resultado de { /pointer } em body e os valores de templateBody.data. Se você deixar false, o markup contido nos valores é renderizado como está. O markup que o próprio autor escreveu e o modelo de templateBody.source não são alvos desde o início, e os campos de cabeçalho também não são afetados |
replyTo | (Opcional) O cabeçalho Reply-To (expressão de valor). Pode ser diferente do remetente (ex.: enviar como no-reply, mas com as respostas indo para um endereço de suporte) |
timeoutMs | (Opcional, 1 ou mais) O tempo limite deste envio (ms). Se ausente, o padrão da plataforma; um valor que ultrapasse o limite máximo é recusado ao salvar |
- O corpo é escrito com
bodyou comtemplateBody, um dos dois. Se você escrever os dois juntos ou deixar os dois vazios, o salvamento é rejeitado. - Por padrão, o valor enviado por quem chama é renderizado como texto. Com
autoEscapeligado, um markup como<a>contido no valor aparece como texto, não como tag. É o mecanismo que impede que um valor enviado por outra pessoa plante links ou tags no e-mail de alguém. Deixefalseapenas quando o markup precisa valer, como no corpo de uma newsletter que o administrador escreveu. - Os campos de cabeçalho não aceitam valores com quebra de linha. Isso vale para
subject,replyToe todos os endereços. Se entrar uma quebra de linha, a execução falha. No corpo, a quebra de linha é conteúdo comum e não é verificada. - O total de destinatários é no máximo 50. Conta-se somando
to(1),ccebcc(o envelope SMTP não distingue cc/bcc e todos saem como destinatários, por isso a contagem é somada). Se ultrapassar, é recusado ao salvar e na execução. Para enviar a muitas pessoas, useResourceForEach+EmailSendpara enviar 1 e-mail por item. - Não vincula resultado. O sucesso significa apenas que "o provedor aceitou o e-mail", portanto não há valor a devolver e ele não aceita
name. Também não faz novas tentativas (o e-mail não é idempotente; tentar de novo após uma falha ambígua causaria envio duplicado, por isso ele não segue oretrydeHttp). A falha é lançada (throw) e tratada pelocatchde umTry. - É uma chamada externa. Ela é contada no limite de chamadas externas por plano e, no orçamento de tempo, entra como
timeoutMs(10 segundos se ausente) uma única vez (como ela não tenta de novo, não se multiplica pela quantidade de tentativas, como emHttp). Pode ser usada dentro deonEachde umResourceForEach(a forma padrão de envio em lote).
{ "type": "EmailSend", "account": { "sys": { "id": "eml_orders" } },
"to": "{ /order/fields/email/en-US }",
"subject": "Pedido recebido com sucesso (número do pedido { /order/sys/id })",
"body": "<p>Seu pedido foi recebido. Avisaremos novamente quando a entrega começar.</p>",
"replyTo": "support@my-shop.example" }Montar o corpo com um modelo (templateBody)
Escrever o corpo do e-mail diretamente na definição do Script obriga a editar o Script a cada mudança de texto. O templateBody mantém o modelo do corpo em um Content e preenche apenas as lacunas dele, de modo que o texto é editado no estúdio de conteúdo e o Script permanece como está.
| Chave | Descrição |
|---|---|
source | Um único ponteiro para o lugar onde está o modelo (obrigatório). Ele precisa apontar para um Content que o ResourceRead ou o ResourceFind deste Script leu, e nenhum outro caractere pode ficar antes ou depois desse ponteiro. O valor tem de ser uma string, portanto um Field multilíngue leva também o locale (/tpl/fields/html/en-US) |
data | Os valores que preenchem as lacunas do modelo (opcional). O nome da chave é o nome da lacuna. É uma posição de dados, portanto um operador precisa do prefixo $ ($cat), e uma lacuna cujo nome começa com $ se escreve $$ (veja Posições de dados e posições de expressão) |
Uma lacuna dentro do modelo se escreve { /<chave> }. Essas lacunas leem apenas data; um modelo não pode ler /payload nem /secrets diretamente. Uma lacuna que aponta para uma chave ausente em data é renderizada como texto vazio. O mesmo vale para uma chave cujo valor é null. É o contrário de um ponteiro em body que aponta para null e imprime o texto null, portanto tenha cuidado ao alternar entre as duas formas.
Onde o modelo tiver um { seguido de / (inclusive com espaço entre eles, por exemplo o a{/*comentário*/} do CSS), isso é lido como uma lacuna, então escreva \{.
Há três lugares em que o salvamento passa, mas a execução para. Falha quando source resolve para um objeto (quando o locale foi esquecido em um Field multilíngue). Falha quando source está vazio (ou o ResourceFind não encontrou nada, ou o Statement que lê o modelo está depois do EmailSend). E falha também quando o resultado de preencher todas as lacunas é apenas espaço em branco.
O modelo para o qual source aponta não passa por escape. O que passa por escape são os valores de data. Por isso, para levar ao e-mail um HTML enviado por quem chama, coloque esse valor em data e deixe autoEscape como 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": "Sua entrega foi iniciada",
"templateBody": {
"source": "{ /tpl/fields/html/en-US }",
"data": { "name": "{ /order/fields/buyerName/en-US }",
"tracking": "{ /order/fields/trackingNo/en-US }" } } }O modelo (o Field html de cnt_mail_shipping) tem este formato.
<p>{ /name }, a entrega do produto que você pediu foi iniciada.</p>
<p>Número de rastreio: { /tracking }</p>Variáveis
SetVar
Declara ou atualiza uma variável mutável com escopo de script. Referencie-a como { /vars/<var> } (o JsonLogic não tem declaração de variável, por isso ela é fornecida como um Statement).
| Campo | Descrição |
|---|---|
var | O nome da variável. Referenciada como { /vars/<var> } |
value | Uma expressão de valor. Pode referenciar a si mesma para acumular |
{ "type": "SetVar", "var": "total", "value": 0 }
{ "type": "SetVar", "var": "total", "value": { "$+": [ "{ /vars/total }", "{ /row/qty }" ] } } // acumula
{ "type": "SetVar", "var": "ids", "value": { "$merge": [ "{ /vars/ids }", [ "{ /row/sys/id }" ] ] } } // coleta em arrayArmazenamento em cache
Cache
Lê e grava no cache de vida curta exclusivo daquele Script. É a posição para segurar por alguns segundos um valor que não compensa buscar de novo a cada execução, como o resultado de uma chamada externa, e reutilizá-lo na chamada seguinte. Não é uma chamada externa, portanto não entra no número de chamadas externas por definição e também não declara tempo algum no orçamento de tempo.
| Campo | Descrição |
|---|---|
action | Um entre "Set" (gravar), "Get" (ler) e "Delete" (remover) (obrigatório) |
key | A chave de cache (Cache Key) (obrigatório). Não é uma expressão de valor, mas um literal (veja abaixo). No máximo 128 caracteres; acima disso, o salvamento é rejeitado |
value | O valor a guardar (exclusivo de Set) |
ttl | O tempo em que o cache permanece vivo (exclusivo de Set, em segundos). Entre 1 e 30; se omitido, 5 |
defaultValue | O valor que Get vincula quando não há dados em cache (exclusivo de Get). Se omitido, null |
name | O nome que guarda o resultado. Em Get é obrigatório (se o valor lido não tem para onde ir, não há razão para lê-lo). Set vincula o valor guardado e Delete vincula se a remoção foi realizada; nos dois é opcional |
- Escreva apenas os campos que correspondem à operação. Se você escrever
ttlem umGetoudefaultValueem umSet, o salvamento é rejeitado. - Não existir e ter expirado não se distinguem. Nos dois casos, o que é vinculado é
defaultValue. O mesmo vale quando o que está guardado énull. - O escopo de armazenamento é aquele Script, e só ele. Outro Script do mesmo Space não enxerga os dados dele, ainda que use a mesma chave de cache. Se aquele Script for editado ou excluído, todos os dados dele desaparecem.
keyé um literal. Deixar que os dados sejam escolhidos por uma chave de cache vinda da requisição faria o chamador decidir o que é lido, e um Script que guarda um valor por membro passaria o valor de um membro a outro. Por isso, um{ /pointer }dentro dekeynão é convertido em valor nem é usado literalmente: o próprio salvamento é rejeitado.- Não pode ficar dentro de uma iteração. Se houver um
Cachedentro do bloco de umLoopou de umResourceForEach, o salvamento é rejeitado. É porque o limite máximo de quantidade abaixo não impõe restrição alguma dentro de uma iteração. Como se grava um dado por volta, o número de Statements escrito na definição e o número de chaves de cache realmente usadas divergem. - Você pode colocar até 5 por definição (incluindo aninhados, somados independentemente da operação). Acima disso, o salvamento é rejeitado.
- O valor guardado vai até 10.240 bytes (10KiB). Acima disso, esse Statement falha (status 422). É igual a qualquer outra falha em tempo de execução, portanto pode ser tratado localmente com
Try/catch.
// reutiliza a taxa de câmbio por 30 segundos.
{ "type": "Cache", "action": "Get", "name": "cached", "key": "rates" }
// se houver um valor guardado, devolve-o tal como está, sem chamada externa
{ "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 }" }
// descarta o valor guardado antes de ele expirar
{ "type": "Cache", "action": "Delete", "key": "rates" }Parse de valores
ParseJson
Faz o parse de um texto JSON no valor que ele representa e o vincula a um nome. Use para o corpo recebido de Http com responseType: "Text", para uma string JSON que chegou no payload, ou para JSON guardado como string em um campo. Não é uma chamada externa, portanto não é contado no limite de chamadas externas e também não declara tempo algum no orçamento de tempo.
| Campo | Descrição |
|---|---|
name | O nome que guarda o valor parseado (obrigatório). Nos outros Statements é opcional, mas aqui é obrigatório. O Statement não faz nada além de vincular seu resultado, então um sem nome não tem efeito algum |
value | O texto JSON a ser parseado (expressão de valor, obrigatório). Aponte para um valor de uma etapa anterior, como em { /resp/body }, ou escreva o texto JSON literalmente (uma { dentro do literal não é lida como template { ponteiro }) |
- Resultado: o próprio valor parseado. Um objeto continua objeto, um array continua array, e um valor único como
42ou"a"também é parseado. Depois você aponta para o interior com{ /<name>/... }. - Um valor já parseado é vinculado sem alteração. Quando
valueresolve para algo que não é string, não há texto a parsear, então esse valor é vinculado como está. - Um
{ /pointer }dentro do texto parseado não é resolvido de novo. Mesmo que uma string recebida de fora contenha uma expressão como{ /payload/... }, ela não é substituída por um valor e permanece texto. nulldistingue dois casos. Se o texto a parsear for apenas a palavranull, isso é normal e o resultado também énull. Já se o lugar apontado porvalueestiver vazio, sem valor nenhum, não há nada a parsear e o Statement falha.- Falha: quando
valueresolve sem valor ou apenas com espaços, e quando o texto não é JSON. Trate comTry/catchcomo qualquer outra falha em tempo de execução; a mensagem de erro leva o texto que se tentou parsear. - Conta como um Statement no número de Statements por definição, mas não tem relação com o limite de chamadas externas nem com o limite de
SetVar.
// 1) Uma API que não devolve JSON: receber como Text e fazer o parse
{ "type": "Http", "method": "GET", "url": "https://api.partner.example/v1/quote",
"responseType": "Text", "name": "resp" },
{ "type": "ParseJson", "name": "quote", "value": "{ /resp/body }" },
// 2) Fazer o parse de uma string JSON que chegou no payload
{ "type": "ParseJson", "name": "spec", "value": "{ /payload/fields/specJson }" }Verificação de assinatura e tratamento de texto
São os Statements que conferem a assinatura que uma processadora de pagamentos enviou por webhook e que desempacotam a string na qual essa assinatura vem embalada. Os três são cálculo, não chamada externa, portanto não são contados no limite de chamadas externas e também não declaram tempo algum no orçamento de tempo; e, como não têm posição de dados, não têm relação com a regra do prefixo $. Um exemplo completo combinando os três está na verificação de assinatura de webhook do cookbook.
Os três Statements têm um limite máximo para o comprimento do valor resolvido. Não é o comprimento da expressão, mas o comprimento do valor que ela aponta (os dezesseis caracteres de { /rawPayload } apontam para dezenas de KB) e, se exceder, a execução falha e pode ser tratada com Try. Os números estão reunidos em Limites de comprimento de valor.
Signature
Confere se o código de assinatura recebido é igual ao código gerado com secret e vincula essa resposta como um valor Boolean. A assinatura que uma processadora de pagamentos (PG, MoR) envia por webhook é verificada com este Statement.
| Campo | Descrição |
|---|---|
name | O nome que guarda o resultado da verificação (obrigatório). { /<name> } é true ou false. Verificar e não usar o resultado equivale a não verificar, por isso não pode ser omitido |
algorithm | O hash com que o código é gerado (obrigatório). SHA1, SHA256, SHA384, SHA512 |
secret | A chave secreta compartilhada com a outra parte (expressão de valor, obrigatório). Declare a chave em secrets e escreva-a como { /secrets/<key> } |
secretEncoding | Em que notação está a string de chave para a qual secret aponta. Utf8 (padrão, chave em texto), Hex, Base64. Deixar em texto uma chave emitida em hex ou base64 resulta em outra chave, de modo que um código plausível é gerado, mas nunca coincide |
value | A mensagem sobre a qual o código é calculado (expressão de valor, obrigatório). Precisa ser literalmente igual aos bytes que a outra parte assinou, portanto normalmente é { /rawPayload }, ou esse valor com o timestamp que o provedor enviou junto no cabeçalho prefixado a ele |
expected | O código que o chamador enviou (expressão de valor, obrigatório). Ex.: { /headers/x-signature } |
- Resultado: um
Boolean. Depois, use{ /<name> }tal como está na condição de umIf. - Escreva
valuecom/rawPayload, não com o/payloadparseado. Transformar de novo em string o payload parseado normaliza espaços, notação numérica e escapes, e não devolve os bytes que a outra parte assinou (Raízes de contexto). - Não existe campo para indicar a notação de saída.
algorithmfixa o comprimento em bytes do código, e hex e base64 de um mesmo comprimento não coincidem em comprimento de string, portanto o motor recupera os bytes sem que a outra parte informe qual notação usou. Pela mesma razão, ele também não distingue maiúsculas de minúsculas no hex, nem base64 de base64url (inclusive com ou sem padding). - A diferença entre falha e
falseestá em quem fornece o valor.- Se
expectednão vier ou o código não coincidir, o resultado é apenasfalse, não é falha. Informar separadamente a ausência do cabeçalho e a divergência do código ensinaria a quem enviou qual dos dois estava errado. - Se
valueestiver vazio, o cálculo é feito com uma mensagem vazia. Um corpo vazio também é objeto de assinatura. - Se
secretnão vier ou não estiver na notação declarada porsecretEncoding, é falha. Dos três casos, esta é a única entrada do próprio autor. A mensagem de falha não levasecretnemvalue.
- Se
- O limite máximo de
valueé 65.536 caracteres (com base no valor resolvido). É um valor ajustado ao tamanho dos corpos de webhook que os provedores realmente enviam. - A comparação decide a igualdade dos valores em constant-time. Quantos bytes iniciais coincidiram não vaza pelo tempo de resposta.
- A chave de assinatura não é escrita literalmente em
secret. Declare-a emsecretsda definição e escreva"{ /secrets/<key> }". Os valores desecretssão criptografados e armazenados com segurança pela WEEGLOO.secretEncodingindica em que notação está a string de chave armazenada emsecrets.
// provedor que assina o corpo inteiro
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
"secret": "{ /secrets/WEBHOOK_SIGNING_SECRET }", "value": "{ /rawPayload }",
"expected": "{ /headers/x-webhook-signature }" }
// provedor que emite a chave em base64 (a string base64 fica guardada como está em secrets)
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
"secret": "{ /secrets/WEBHOOK_SIGNING_KEY }", "secretEncoding": "Base64",
"value": "{ /rawPayload }", "expected": "{ /headers/webhook-signature }" }Hash
Faz o digest de value e o vincula como uma string na notação definida por encoding. Use-o para reproduzir esquemas de assinatura que, em vez de HMAC, "concatenam alguns campos com a chave secreta e calculam SHA256".
| Campo | Descrição |
|---|---|
name | O nome que guarda o digest (obrigatório) |
algorithm | MD5, SHA1, SHA256, SHA384, SHA512 (obrigatório). MD5 existe para reproduzir esquemas antigos que o exigem, e não é um valor a escolher para uma assinatura nova |
value | A mensagem a digerir (expressão de valor, obrigatório) |
encoding | A notação do resultado. Hex (padrão), HexUpper, Base64, Base64Url |
- Não existe campo
secret. Dentro devalue, escreva{ /secrets/<key> }na posição em que aquele esquema coloca a chave. Cada esquema põe a chave no início, no fim ou no meio, e escrevendo assim é possível expressar qualquer uma dessas posições. Declare a chave antes emsecrets. - Resultado: uma string. Para comparar com o código que a outra parte enviou, escreva
{ "==": [ "{ /<name> }", "{ /headers/... }" ] }. Essa comparação, diferente da comparação constant-time deSignature, é uma comparação de igualdade comum. - Se
valueresolver sem valor algum ou apenas com espaços, é falha (por ser uma expressão do próprio autor). - O limite máximo de
valueé 128 caracteres. Por ser a posição que guarda uns poucos campos concatenados, é muito mais estreito que o deSignature. Se você precisar calcular sobre o corpo inteiro de um webhook, useSignature.
// SHA256(número do pedido + valor + MERCHANT_KEY) em hex maiúsculo
{ "type": "Hash", "name": "expectedSign", "algorithm": "SHA256", "encoding": "HexUpper",
"value": "{ /payload/orderId }{ /payload/amount }{ /secrets/MERCHANT_KEY }" }Regex
Aplica pattern a value e vincula o que mode pediu. Como as expressões de valor não têm nenhum meio de cortar strings (há apenas cat, que concatena, e in, que verifica inclusão), use este Statement para desempacotar algo que chega com vários valores embalados em um só cabeçalho, como t=…,v1=….
| Campo | Descrição |
|---|---|
name | O nome que guarda o resultado (obrigatório). Em Capture, os elementos são apontados como { /<name>/1 } |
mode | "Match" vincula como Boolean se houve correspondência; "Capture" vincula a primeira correspondência como array (obrigatório) |
pattern | A expressão regular (obrigatório). Não é uma expressão de valor, mas um literal (veja abaixo). As flags são escritas dentro do padrão, como (?i). No máximo 128 caracteres; acima disso, o salvamento é rejeitado |
value | O texto ao qual aplicar o padrão (expressão de valor, obrigatório). Se o valor resolvido passar de 10.240 caracteres (10KiB), a execução falha |
- Resultado:
MatchéBoolean;Captureé um array ounull. No array, o índice0é a correspondência inteira e, de1em diante, vêm os grupos de captura; um grupo que não participou énull(não uma string vazia, que significaria ter havido correspondência). Se o padrão não aparecer,Captureénull, e não um array vazio. - Os dois modos perguntam "o padrão aparece em algum lugar?". Se o texto inteiro precisar ser igual ao padrão, ancore-o com
^…$. A pergunta foi mantida igual nos dois para que os dois Statements, um verificando comMatche outro extraindo comCapture, não devolvam respostas diferentes. patterné um dos três campos deste motor que não são expressões de valor (os outros são akeydeCachee acolordePdfWatermark). Executar tal e qual um padrão vindo da requisição deixaria o chamador escolher a expressão a ser executada, e o backtracking das expressões regulares transformaria isso em um meio de negação de serviço. Por isso, um{ /pointer }dentro do padrão também não é convertido em valor: passa a fazer parte do padrão, literalmente.- O padrão é compilado uma única vez, para toda a definição, quando a execução começa. Mesmo dentro de um
Loopou de umResourceForEach, ele não é recompilado a cada iteração, e um padrão inutilizável falha antes que o primeiro Statement faça qualquer coisa (tratável comTry).
// desempacota "t=1492774577,v1=<64 caracteres hex>" em { /sig/1 } = timestamp, { /sig/2 } = código
{ "type": "Regex", "name": "sig", "mode": "Capture",
"pattern": "^t=(\\d+),v1=([0-9a-f]{64})$", "value": "{ /headers/x-provider-signature }" }
// verifica apenas o formato
{ "type": "Regex", "name": "isOrderId", "mode": "Match",
"pattern": "^ORD-\\d{8}-\\d{4}$", "value": "{ /payload/orderId }" }Controle de fluxo
If
Uma ramificação condicional. condition é JsonLogic, e verdadeiro/falso segue as regras de Avaliação de verdadeiro e falso.
| Campo | Descrição |
|---|---|
condition | JsonLogic (avaliado como booleano) |
then | O array de Statement a executar quando verdadeiro |
else | (Opcional) O array de Statement a executar quando falso |
{ "type": "If",
"condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" } } ],
"else": [ /* ... */ ] }Loop
Iteração. Escolha um modo: over (foreach), while (condição) ou for (contado). Em qualquer modo, o motor impõe um limite máximo de iterações (para evitar loops infinitos). Esse limite é declarado em maxIterations e, se você não o escrever, aplica-se o limite máximo da plataforma. Dentro de body você também pode incluir chamadas externas (Http, EmailSend) e ingestão de arquivo de Media, e os Statements de chamada externa são de fato chamados a cada iteração na execução. O limite de número máximo de chamadas externas por definição continua valendo.
No orçamento de tempo, ele entra como multiplicação. O tempo que este Statement declara é o tempo declarado por body multiplicado por maxIterations (10.000 se não houver) (veja Orçamento de tempo). Se não houver chamada externa em body, o tempo declarado é 0, portanto o orçamento base de 30 segundos é o limite efetivo.
| Campo | Descrição |
|---|---|
over | foreach: uma expressão de valor que resolve para um array |
while | condição: JsonLogic (repete enquanto verdadeiro) |
for | contado: { "from", "to", "step"? }. De from até to inclusive; step tem padrão 1 |
maxIterations | O número máximo de iterações (opcional). Se você não o escrever, aplica-se o limite máximo da plataforma, 10.000, e um valor maior que esse é recusado ao salvar |
name | (Opcional) O nome ao qual vincular o item atual (foreach) ou o índice (while, for) ({ /<name> }) |
body | O array de Statement do corpo do loop |
// 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
Executa as branches de forma concorrente e prossegue após a junção delas. Referências entre branches não são permitidas (se houver uma dependência, coloque-as sequencialmente).
| Campo | Descrição |
|---|---|
branches | Statement[][]. Cada elemento é uma branch (um array de Statements) |
{ "type": "Parallel", "branches": [
[ { "type": "Http", "method": "GET", "url": "https://api.a.com/x", "name": "a" } ],
[ { "type": "Http", "method": "GET", "url": "https://api.b.com/y", "name": "b" } ]
] }Return
É o return da programação comum. Retorna o resultado do Script ao chamador e encerra normalmente naquele ponto.
| Campo | Descrição |
|---|---|
value | (Opcional) A expressão de valor a retornar |
isError | Padrão false. Quando true, value retorna como o error da resposta (caso contrário, como return) |
statusCode | O código de status da resposta. Padrão 200 |
- Se
Returnnunca for alcançado, não há valor de retorno. Para retornar um resultado, especifiquevalueexplicitamente. - Por ser um encerramento normal, não uma exceção ou throw, não é alvo de
catch(mesmo dentro deTry, encerra o Script inteiro, masfinallyainda é executado). - Um guard também é expresso com este Statement. Se você colocar um
Returnnothende umIf, ele retorna um valor quando a condição é violada e não executa os Statements seguintes. Este é um dos vários usos deReturn.
{ "type": "Return", "value": { "orderId": "{ /order/sys/id }", "status": "paid" }, "statusCode": 201 }
{ "type": "Return", "value": { "reason": "payment failed" }, "isError": true, "statusCode": 402 }Try
Tratamento de exceções.
| Campo | Descrição |
|---|---|
body | O array de Statement a tentar |
catch | (Opcional) Executa quando body falha. Expõe { message } em /error (em qual Statement a falha ocorreu não é incluído) |
finally | (Opcional) Executa sempre, independentemente de sucesso ou falha |
- Se
catcho tratar, o Script não é interrompido. Apenas uma falha semcatchinterrompe o Script (incluindo uma tentativa de compensação). - O que conta como "falha" e os limites da compensação são abordados em Semântica de execução, restrições e segurança.
{ "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": [ /* sempre é executado */ ] }Documentos relacionados
- Expressões de valor: as regras de valor que todos os campos acima seguem.
- Semântica de execução, restrições e segurança: ordem de execução, erros, restrições estáticas e segurança.
- Cookbook: exemplos completos que combinam esses Statements.
- Visão geral do Script: a estrutura de nível superior e o tempo concedido a uma execução.
