動的評価
サンドボックス環境と制御されたモジュールアクセスにより、コードを実行時に動的に実行します。
2つのシステム
Wippyは2つの評価システムを提供しています:
| システム | 目的 | ユースケース |
|---|---|---|
expr |
式評価 | 設定、テンプレート、シンプルな計算 |
eval_runner |
完全なLua実行 | プラグイン、ユーザースクリプト、動的コード |
exprモジュール
expr-lang構文を使用した軽量な式評価。
local expr = require("expr")
local result, err = expr.eval("x + y * 2", {x = 10, y = 5})
-- result = 20
式のコンパイル
一度コンパイルして何度も実行:
local program, err = expr.compile("price * quantity")
local total1 = program:run({price = 10, quantity = 5})
local total2 = program:run({price = 20, quantity = 3})
サポートされる構文
-- 算術
expr.eval("1 + 2 * 3") -- 7
expr.eval("10 / 2 - 1") -- 4
expr.eval("10 % 3") -- 1
-- 比較
expr.eval("x > 5", {x = 10}) -- true
expr.eval("x == y", {x = 1, y = 1}) -- true
-- ブール
expr.eval("a && b", {a = true, b = false}) -- false
expr.eval("a || b", {a = true, b = false}) -- true
expr.eval("!a", {a = false}) -- true
-- 三項演算子
expr.eval("x > 0 ? 'positive' : 'negative'", {x = 5})
-- 関数
expr.eval("max(1, 5, 3)") -- 5
expr.eval("min(1, 5, 3)") -- 1
expr.eval("len([1, 2, 3])") -- 3
-- 配列
expr.eval("[1, 2, 3][0]") -- 1
-- 文字列連結
expr.eval("'hello' + ' ' + 'world'")
eval_runnerモジュール
セキュリティ制御付きの完全なLua実行。
local runner = require("eval_runner")
local result, err = runner.run({
source = [[
local function double(x)
return x * 2
end
return double(input)
]],
args = {21}
})
-- result = 42
設定
| パラメータ | 型 | 説明 |
|---|---|---|
source |
string | Luaソースコード(必須) |
method |
string | 返されたテーブル内で呼び出す関数 |
args |
any[] | 関数に渡す引数 |
modules |
string[] | 許可される組み込みモジュール |
imports |
table | インポートするレジストリエントリ |
context |
table | ctxとして利用可能な値 |
allow_classes |
string[] | 追加のモジュールクラス |
custom_modules |
table | モジュールとしてのカスタムテーブル |
モジュールアクセス
許可されるモジュールをホワイトリスト化:
runner.run({
source = [[
local json = require("json")
return json.encode({hello = "world"})
]],
modules = {"json"}
})
リストにないモジュールはrequireできません。
レジストリインポート
レジストリからエントリをインポート:
runner.run({
source = [[
local utils = require("utils")
return utils.format(data)
]],
imports = {
utils = "app.lib:utilities"
},
args = {{key = "value"}}
})
カスタムモジュール
カスタムテーブルを注入します:
runner.run({
source = [[
return sdk.version
]],
custom_modules = {
sdk = {version = "1.0.0", api_key = "xxx"}
}
})
コンテキスト値
ctxとしてアクセス可能なデータを渡す:
runner.run({
source = [[
return "Hello, " .. ctx.user
]],
context = {user = "Alice"}
})
プログラムのコンパイル
繰り返しの実行のために一度コンパイル:
local program, err = runner.compile([[
local function process(x)
return x * 2
end
return { process = process }
]], "process", {modules = {"json"}})
local result = program:run({10}) -- 20
セキュリティモデル
モジュールクラス
モジュールは機能によって分類:
| クラス | 説明 | デフォルト |
|---|---|---|
deterministic |
純粋関数 | 許可 |
encoding |
データエンコーディング | 許可 |
time |
時間操作 | 許可 |
nondeterministic |
ランダムなど | 許可 |
process |
spawn、レジストリ | ブロック |
storage |
ファイル、データベース | ブロック |
network |
HTTP、ソケット | ブロック |
ブロックされたクラスの有効化
runner.run({
source = [[
local http = require("http_client")
return http.get("https://api.example.com")
]],
modules = {"http_client"},
allow_classes = {"network"}
})
権限チェック
システムは以下の権限をチェック:
eval.compile- コンパイル前eval.run- 実行前eval.module- ホワイトリスト内の各モジュールeval.import- 各レジストリインポートeval.class- 各許可されたクラス
これらはセキュリティポリシーで設定します。
エラー処理
local result, err = runner.run({...})
if err then
if err:kind() == errors.PERMISSION_DENIED then
-- セキュリティポリシーによりアクセス拒否
elseif err:kind() == errors.INVALID then
-- 無効なソースまたは設定
elseif err:kind() == errors.INTERNAL then
-- 実行またはコンパイルエラー
end
end
ユースケース
プラグインシステム
local plugins = registry.find({meta = {type = "plugin"}})
for _, plugin in ipairs(plugins) do
local source = plugin:data().source
runner.run({
source = source,
method = "init",
modules = {"json", "time"},
context = {config = app_config}
})
end
テンプレート評価
local template = "Hello, {{name}}! You have {{count}} messages."
local compiled = expr.compile("name")
-- 高速な繰り返し評価
for _, user in ipairs(users) do
local greeting = compiled:run({name = user.name})
end
ユーザースクリプト
local user_code = request:body()
local result, err = runner.run({
source = user_code,
modules = {"json", "text"}, -- 安全なモジュールのみ
context = {data = input_data}
})