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 duas: o pattern de Regex e a key de Cache (veja Regex e Cache).
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 }) |
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) 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.
ServiceUser (o membro inscrito no produto) é somente leitura. Apenas os três statements de leitura (ResourceRead·ResourceFind·ResourceForEach) aceitam esse valor; se você o escrever em um statement de escrita, o salvamento é rejeitado (consulte Erros). As regras são abordadas em Leitura do diretório de membros.
Escritas de recurso
Todo statement de escrita tem propagateEvents (padrão false). 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" }(ambos obrigatórios). 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" }ResourceUpdate
Substitui totalmente os campos do Content ou Media 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" ou "Media" |
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) |
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 ou Media 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" ou "Media" |
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) |
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 Leitura do 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.
Leitura do 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 são comuns aos três statements.
- Somente leitura.
ResourceCreate·ResourceUpdate·ResourcePatch·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: no Script não existe caminho algum para alterar um membro, portanto a recusa não vem de um erro de permissão, mas de um statement escrito de forma inválida. - 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 (Leitura do 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 e, no orçamento de tempo, entra como timeoutMs (30 segundos se ausente) × (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", "secret"? }]. value é uma expressão de valor. Um cabeçalho secret:true é tratado como exclusivo da CMA (administrador): não é exposto aos usuários finais e é descriptografado apenas imediatamente antes de a requisição ser enviada. 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 |
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 |
- 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 sk-...", "secret": true } ],
"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" }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, obrigatório). 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 |
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 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" }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) |
secretEncoding | Em que notação você escreveu secret. 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.
secretnão é armazenado de forma criptografada. Diferente dosecret: truede um cabeçalhoHttp(armazenado criptografado e descriptografado imediatamente antes do envio), ele permanece exatamente como escrito na definição, portanto seu valor fica visível para os papéis que podem ler aquele Script. Um membro (ServiceUser) não consegue ler a definição de um Script (a autoria e a consulta são exclusivas da CMA).
// provedor que assina o corpo inteiro
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
"secret": "whsec_9f2c1b7ae4", "value": "{ /rawPayload }",
"expected": "{ /headers/x-webhook-signature }" }
// provedor que emite a chave em base64
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
"secret": "aGVsbG8td2VlZ2xvbw==", "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. Como cada esquema coloca a chave no início, no fim ou no meio, escrever a chave diretamente dentro devalueé o que permite expressar todas as posições. - 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 + merchantKey) em hex maiúsculo
{ "type": "Hash", "name": "expectedSign", "algorithm": "SHA256", "encoding": "HexUpper",
"value": "{ /payload/orderId }{ /payload/amount }9f2c1b7ae4" }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 dois campos deste motor que não são expressões de valor (o outro é akeydeCache). 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.
