値式 (Value Expressions)

Script で値が必要となるすべての箇所(URL、リクエスト body、フィールド値、条件、フィルター値、対象 id など)は、以下の 3 つの形式のいずれかです。例外は 2 つだけです。RegexpatternCachekeyはリテラルとしてのみ書き、その中の { /pointer } は値に置き換えられません。このドキュメントでは、その 3 つの形式と、値がどこから来るのか(コンテキストルート)、そして WEEGLOO データ特有のロケールマップのルールを説明します。Statement カタログのすべてのフィールドがこのルールに従います。

3 つの形式

形式ルール
参照 (reference)文字列内の { /json-pointer } をコンテキストに対して resolve します。"{ /payload/fields/title }"
リテラル (literal){ /ptr } を含まない値(文字列、数値、ブーリアン、オブジェクト、配列)です。そのまま使用します。"draft", 42, true, { "a": 1 }
演算と条件 (JsonLogic)単一のキーが演算子であるオブジェクトです。そのオペランドは、それ自体が値式(参照、リテラル、ネストした演算)です。位置によっては演算子に $ が必要です。{ "$+": [ "{ /vars/n }", 1 ] }

3 つの形式はネストできます。JsonLogic のオペランドに参照を、参照の結果を再び演算に入れる形で組み合わせます。

データ位置と式位置: $ をいつ付けるか

同じ JSON でも、位置によって読まれ方が変わります。分かれ目はその位置のキーを誰が所有しているかです。fields のキーは Content Type のフィールド id であり、Http.body のキーは呼び出し先 API のスキーマなので、そうした位置での catin は演算子ではなくフィールド名でなければなりません。

位置該当するフィールド読まれ方
データ位置fields(ResourceCreate, ResourceUpdate, ResourcePatch), Http.body, Return.value, SetVar.value, Cache.value, Cache.defaultValue$ が付いていないキーは常にフィールド名です。演算を使うには $ を付けます。
式位置If.condition, Loop.while, version値全体が式です。演算子は cat でも $cat でも構いません。
テンプレート位置それ以外すべて(url, method, headers[].value, locale, order, over, target.sys.id, EmailSend のフィールド、SignatureHashRegex の値フィールド)文字列なので { /pointer } だけが入ります。
リテラル専用Regex.pattern, Cache.key値式ではありません。Regex.pattern に書いた { /pointer } は置換されず、パターンの一部になります。

ルールは 2 行です。

  1. データ位置で $ が付いていないキーは、常にフィールド名です。演算を使うには演算子に $ を付けます。
  2. いったん $ で式に入ったら、その内側はすべて式です。ネストした演算子に $ は不要です(付けても構いません)。

迷ったら、すべての演算子に $ を付けてください。どの位置でも正しくなります。

// データ位置: cat は Content Type のフィールド名です (連結の演算ではありません)
"fields": { "cat": { "en-US": "hello" } }
 
// データ位置での計算: 境界にだけ $、その内側はそのまま
"fields": { "tier": { "en-US": { "$if": [ { ">=": [ "{ /p/score }", 700 ] }, "gold", "silver" ] } } }
 
// 式位置: そのまま書きます
"condition": { "and": [ { "<": [ "{ /a/body/risk }", 0.5 ] }, { ">=": [ "{ /b/body/score }", 700 ] } ] }

$ で始まるフィールド名が必要なとき: $$

JSON Schema の $ref$schema のように、キーが実際に $ で始まらなければならない場合は、$ を 2 回書きます。"$$ref" はデータキー $ref を意味します。先頭の $1 つだけ取り除かれ($$$ref$$ref になります)、キーにのみ適用されます(値の中の $ はそのままです)。

"body": { "$$ref": "#/components/schemas/Item", "topK": { "$min": [ "{ /payload/fields/k }", 50 ] } }

拒否される 2 つのケース

以下の 2 つの場合は、黙って別の意味に解釈されるのではなく、エラーとして拒否されます。

  • $ キーが同じオブジェクトの他のキーと同居している場合はエラーです。演算はそのオブジェクトの唯一のキーでなければならず、兄弟のデータは 1 段外に出せば済みます。
  • 未知の $ キーはエラーです。$catt$catt という名前のフィールドではありません。$ の名前空間は演算子用に予約されています。

式位置では、演算子名が兄弟キーと同居しているのもエラーです({ "and": […], "or": […] })。その位置にはデータという解釈がなく、すべてのオブジェクトは真と判定されるため、そのままにすると条件が黙って常に真になります。

参照: { /json-pointer }

波括弧の中に RFC 6901 JSON Pointer(必ず / で始まる)を入れます。波括弧の周囲の空白は許可されます({ /a/b }{/a/b} と同じです)。

単一ポインターと混合テンプレート: 型のルール

  • 文字列全体が単一ポインターの場合、その値の元の型をそのまま維持します(数値なら数値、オブジェクトならオブジェクト、配列なら配列)。
  • リテラルテキストと混ざると文字列として連結(concatenation) されます。
"{ /payload/fields/count }"                 // 数値なら数値のまま (例: 42)
"{ /payload/fields/tags }"                  // 配列なら配列のまま
"page-{ /payload/fields/n }-of-10"          // 文字列連結 → "page-42-of-10"
"Bearer { /payload/fields/token }"          // 文字列連結 → "Bearer abc123"

存在しない値

  • パスが存在しないか値が空の場合、単一ポインターは null混合テンプレートは空文字列として処理されます。

コンテキストルート: 値はどこから来るのか

{ /pointer } の最上位セグメントは、以下の 7 つのいずれかです。

ルート内容
/payload呼び出し時に渡された JSON payload(入力)です。例: { /payload/fields/email }
/rawPayload同じ入力を、呼び出し元が送った本文の文字列そのままで保持します(パース前)。例: { /rawPayload }
/headers呼び出し時に渡されたリクエスト HTTP ヘッダーです。キーは小文字で、名前ごとに単一の値です。例: { /headers/authorization }
/now実行が始まった時刻です。{ /now/seconds }{ /now/millis }{ /now/iso }
/<name>その name を持つ、先行する statement の結果です。例: { /order/sys/id }
/vars/<name>SetVar で宣言した script-scoped な可変変数です。例: { /vars/total }
/errorTrycatch ブロック内でのみ使用します。捕捉したエラー { message } です。例: { /error/message }

/<name> を除いた 6 つの名前(payloadrawPayloadheadersnowvarserror)は予約されているため、statement の name には使えません。同じ名前を使うとそのルートを上書きしてしまうので、保存の時点で拒否されます(共通フィールドのバインディング名の規則)。

/rawPayload: 送られたままの本文

/payload はパースされた値で、/rawPayload同じ本文の原文の文字列です。2 つは同じものを指していますが、同じではありません。パースされた値を再び文字列に戻すと、空白、数値表記、エスケープ、重複キーがすべて正規化され、送られたバイトには戻りません。

そのため、送られたバイトの上で計算される値は /rawPayload でしか扱えません。 代表的なのが決済代行会社の Webhook の署名検証です(Signature)。値を取り出して使う普段の参照は /payload で行います。

呼び出しの本文は JSON オブジェクトのみを受け取ります。本文が空なら無いものとして扱い、JSON オブジェクトでなければ(壊れた JSON、配列、スカラー、リテラルの null)実行せずに拒否します(エラー 参照)。

/now: 実行が始まった時刻

/now は、この実行が始まった時刻を 3 つの形で保持します。

ポインター
{ /now/seconds }epoch 秒(整数)
{ /now/millis }epoch ミリ秒(整数)
{ /now/iso }sys.createdAt と同じ、プラットフォームの時刻表記の文字列(UTC)
  • 1 回の実行に時刻は 1 つだけです。 時計を読む statement ではなく、実行が始まるときに埋め込んでおく値なので、2 つの文が互いに違う値を見ることはありません。Parallel の各ブランチも同じ時刻を受け継ぎます。文ではないので statement の数にも数えません。
  • タイムゾーンを選ぶフィールドはありません。 epoch の値はどこでも同じ数であり、iso は UTC 表記です。
  • Webhook の replay window(署名に載ったタイムスタンプが今から何秒以内か)の検証に使います。タイムスタンプは通常文字列で入ってきますが、算術演算が数値に変換してくれるのでそのまま比較します。
// 署名に載ったタイムスタンプが 5 分(300 秒)以内か
{ "<": [ { "-": [ "{ /now/seconds }", "{ /sig/1 }" ] }, 300 ] }

statement の結果の形

name を持つ statement の結果の形は、型ごとに異なります。

statement結果の形参照例
Http{ status, body }{ /resp/status }, { /resp/body/choices/0/message/content }
ResourceCreate, ResourceRead(単一), ResourceFind(単一)リソースそのもの{ /post/sys/id }, { /post/fields/title/en-US }
ResourceForEach(巡回中)name現在の項目 = リソースそのもの。onEach 内でのみ参照{ /post/sys/id }, { /post/fields/title/en-US }
ParseJsonパースされた値そのもの(オブジェクト・配列・スカラー){ /quote/items/0/price }
SignatureBoolean(検証を通ったかどうか){ /verified }
Hash文字列(宣言した表記のダイジェスト){ /expectedSign }
RegexMatchBooleanCapture は配列(0=マッチ全体、1 からがキャプチャグループ)、マッチがなければ null{ /isOrderId }, { /sig/1 }
  • ResourceFind はマッチがない場合 null をバインドします。{ "==": [ "{ /found }", null ] } で存在の有無を分岐します。
  • ResourceRead(単一)は対象が存在しない場合はエラーです(Try で処理できます)。詳しくは Statement カタログのリソース読み取りで扱います。
  • ServiceUser を読み取ると、結果は会員リソースそのものです({ /member/sys/id })。ContentMedia と違い、フィールドがロケールマップではなく値そのままです。規則は 会員ディレクトリの読み取りで扱います。

演算と条件: JsonLogic

計算や条件が必要なときは、jsonlogic.com 仕様の演算子オブジェクトを使います。

  • データアクセスは vanilla の var(dot-path) ではなく { /ptr } 参照に統一します。エンジンはまずオペランドのポインターを resolve し、その後で演算子を適用します。
  • 演算子はそのオブジェクトの唯一のキーでなければなりません。データ位置では $ を付けたキーだけが演算で、式位置では $ の有無に関係なく演算です(データ位置と式位置 を参照)。

演算子の表

表に載っている名前は演算子トークンです。データ位置で使うときは先頭に $ を付けます(cat なら $cat)。式位置ではどちらでも構いません。

分類演算子意味と例
条件if (別名 ?:){ "if": [条件, 真の値, 条件2, 真の値2, …, 既定値] }。最初に真になった条件の値、なければ最後の既定値。
論理and, or短絡評価。and は最初の falsy(または最後)、or は最初の truthy(または最後)を値として返します。
論理! (not), !! (to-bool){ "!": x } は truthy の否定、{ "!!": x } は truthy かどうかです。存在チェックには !! をよく使います。
等価==, !=緩い比較(数値へ強制変換してから比較。"1"==1 は真)。
等価===, !==厳密な比較(型まで含む)。
比較<, <=, >, >=連鎖可能: { "<": [1,2,3] }1<2 AND 2<3 です。数値化できない(NaN)場合は false です。
算術+すべてのオペランドの合計。
算術-オペランドが 1 個なら符号反転、2 個なら減算。
算術*, /, %乗算、除算、剰余。
集計min, maxオペランドの最小値と最大値。
文字列catすべてのオペランドを文字列として連結。
包含in{ "in": [needle, haystack] }。haystack が文字列なら部分文字列、コレクションなら要素の包含。
配列merge複数の配列や値を 1 つの配列に平坦化(累積収集に使う)。

配列を反復する演算子(map, filter, reduce, all, some, none)はサポートしていません。Script は配列を Loop で反復します(Statement カタログの Loop)。

数値変換と例

数値変換のルールは次のとおりです。数値はそのままにし、true は 1、false は 0、文字列はパースし(パースできない場合は計算が失敗します)、null は 0 に変換します。

以下のスニペットは式位置を前提としています。データ位置(fields, Http.body, Return.value, SetVar.value)に入れるときは、最上位の演算子に $ を付け、内側のオペランドはそのままにします。

{ "-":  [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] }   // 残高 - 費用
{ "<":  [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] }   // 残高 < 費用 → boolean
{ "and": [ { "<": [ "{ /a/body/risk }", 0.5 ] }, { ">=": [ "{ /b/body/score }", 700 ] } ] }
{ "cat": [ "id-", "{ /payload/sys/id }" ] }                                    // "id-<uuid>"
{ "!!": "{ /found/sys/id }" }                                                  // 存在すれば true
{ "$merge": [ "{ /vars/ids }", [ "{ /row/sys/id }" ] ] }                       // 配列の蓄積: SetVar.value はデータ位置なので $
{ "if": [ "{ /payload/fields/next }", "{ /payload/fields/next }", "END" ] }     // next があれば next、なければ "END"

真偽の判定 (Truthiness)

ifandor!!!If.conditionLoop.while は、以下のルールで真偽を判定します。

  • falsy: nullfalse、数値 0、空文字列 ""、空のコレクション(空の配列)。
  • truthy: それ以外すべて(0 でない数値、空でない文字列と配列、すべてのオブジェクト)。

キーも参照できます

fields のようなマップのキー{ /ptr } 参照をサポートします。キーは実行時に resolve されます。

"fields": { "{ /payload/fields/fieldName }": { "en-US": "{ /payload/fields/fieldValue }" } }

2 つのキーが同じ値に resolve されると、衝突してエンジンエラーになります。

ロケールマップ: Content と Media 特有のルール

WEEGLOO の ContentMedia の各フィールドは、値ではなくロケールごとのマップです(例: balance{ "en-US": 1, "ko-KR": 10 })。そのため、読み書きの際はロケールも一緒に扱う必要があります。Mediatitledescription(スカラー)、file(インジェスト指示)がロケールマップです。/payload や HTTP レスポンスのように Content でも Media でもない JSON は、このルールとは無関係です(スキーマが定めた構造そのままで、スカラーならスカラーです)。

読み取り

  • スカラーを得るには ロケールまで指定します: { /<name>/fields/<field>/<locale> }(例: { /post/fields/title/en-US })。
  • ロケールを指定せず { /<name>/fields/<field> } とすると、ロケールマップのオブジェクト全体が返ります。
  • localized:false のフィールドは既定ロケールのバケットにのみ存在するため、その既定ロケールのコードで読み取ります。

書き込み (ResourceCreate, ResourceUpdate, ResourcePatch の fields)

値はロケールマップ { "<locale>": <スカラー値式> } です。読み取りと対称です。

"fields": {
  "title":  { "en-US": "Hello", "ko-KR": "안녕" },   // 複数のロケールはバケットを列挙
  "status": { "en-US": "paid" }
}
  • ResourceCreate は populate するすべてのフィールドに Space の既定ロケールのバケットを必ず含める必要があります(default-locale ルール)。
  • ResourceUpdate は全体の置き換えです。fields にないフィールドとロケールは削除されます(file を含む)。
  • ResourcePatch は指定したフィールドとバケットのみを更新します(残りのフィールドとロケールは維持)。
  • リテラルの null で削除します: 値がリテラルの null であれば、その (field, locale) バケットを削除します(Patch で特定のロケールを空にする際の標準)。""(空文字列)は削除ではなく空の値の設定です。値式({ /ptr })が実行時に null と評価される場合は、削除ではなくエラーです(payload の欠落を黙って飲み込むことはありません)。削除はリテラルの null のみが該当します。
  • Mediafile: 値はスカラーではなくインジェスト指示 { "source": …, "encoding": "url"|"base64" } です。インジェストが実際に何をするのかは Statement カタログの ResourceCreateで扱います。
  • localized:false のフィールドは既定ロケールのバケットにのみ入れます。
  • ロケールコード(マップのキー)も { /ptr } 参照が可能です(上記 キーも参照できます を参照)。動的なロケールを作るときに使います。

locale 便宜フィールド

ResourceCreateResourceUpdateResourcePatchlocale を与えると、エンジンが fields各値を自動的に { <locale>: 値 } バケットで包みます。 つまり、スカラーだけを与えればよいのです。

// 以下の2つは同じ
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
  "locale": "en-US", "fields": { "title": "Hello" } }
 
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
  "fields": { "title": { "en-US": "Hello" } } }

locale を与えつつ、値にすでにロケールマップ({ "en-US": … })をネストしていると、{ <locale>: { "en-US": … } } のように二重にネストされます(作成者のミス)。locale を使うならスカラーのみ、使わないなら明示的なロケールマップのみ、というように 1 つに統一します。

where と order のロケール

  • whereorder では、fields.X にエンジンが Space の既定ロケールを自動的に適用します(CMA の照会と同じ)。
  • 特定のロケールを指定するには、fields.X.<locale> のように明示します。
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }   // 既定ロケールの slug
"where": { "fields.title.ko-KR": { "prefix": "안" } }              // 特定のロケール

エラー

値式の規則に違反したときに出るコードです。保存時に検査され、定義のほかの静的制約に反したコードは 実行セマンティクス、制約、セキュリティのエラー に、呼び出し時に出るコードは エンドポイントのエラー にあります。すべてのリソースに共通するコードは 共通エラー を参照してください。

コード条件
WGL400056データ位置で、$ の演算キーを同じオブジェクトの他のキーと同居させました。
WGL400055データ位置に、演算子として定義されていない $ キーを書きました。