Dynamische Auswertung

Führen Sie Code dynamisch zur Laufzeit mit Sandbox-Umgebungen und kontrolliertem Modulzugriff aus.

Zwei Systeme

Wippy bietet zwei Auswertungssysteme:

System Zweck Anwendungsfall
expr Ausdrucksauswertung Konfiguration, Templates, einfache Berechnungen
eval_runner Vollständige Lua-Ausführung Plugins, Benutzerskripte, dynamischer Code

expr-Modul

Leichtgewichtige Ausdrucksauswertung mit expr-lang-Syntax.

local expr = require("expr")

local result, err = expr.eval("x + y * 2", {x = 10, y = 5})
-- result = 20

Ausdrücke kompilieren

Einmal kompilieren, mehrfach ausführen:

local program, err = expr.compile("price * quantity")

local total1 = program:run({price = 10, quantity = 5})
local total2 = program:run({price = 20, quantity = 3})

Unterstützte Syntax

-- Arithmetik
expr.eval("1 + 2 * 3")           -- 7
expr.eval("10 / 2 - 1")          -- 4
expr.eval("10 % 3")              -- 1

-- Vergleich
expr.eval("x > 5", {x = 10})     -- true
expr.eval("x == y", {x = 1, y = 1}) -- true

-- Boolean
expr.eval("a && b", {a = true, b = false})  -- false
expr.eval("a || b", {a = true, b = false})  -- true
expr.eval("!a", {a = false})     -- true

-- Ternär
expr.eval("x > 0 ? 'positive' : 'negative'", {x = 5})

-- Funktionen
expr.eval("max(1, 5, 3)")        -- 5
expr.eval("min(1, 5, 3)")        -- 1
expr.eval("len([1, 2, 3])")      -- 3

-- Arrays
expr.eval("[1, 2, 3][0]")        -- 1

-- String-Verkettung
expr.eval("'hello' + ' ' + 'world'")

eval_runner-Modul

Vollständige Lua-Ausführung mit Sicherheitskontrollen.

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

Konfiguration

Parameter Typ Beschreibung
source string Lua-Quellcode (erforderlich)
method string Aufzurufende Funktion in zurückgegebener Tabelle
args any[] An Funktion übergebene Argumente
modules string[] Erlaubte eingebaute Module
imports table Registry-Einträge zum Importieren
context table Als ctx verfügbare Werte
allow_classes string[] Zusätzliche Modulklassen
custom_modules table Benutzerdefinierte Tabellen als Module

Modulzugriff

Erlaubte Module auf Whitelist setzen:

runner.run({
    source = [[
        local json = require("json")
        return json.encode({hello = "world"})
    ]],
    modules = {"json"}
})

Module, die nicht in der Liste sind, können nicht mit require geladen werden.

Registry-Imports

Einträge aus der Registry importieren:

runner.run({
    source = [[
        local utils = require("utils")
        return utils.format(data)
    ]],
    imports = {
        utils = "app.lib:utilities"
    },
    args = {{key = "value"}}
})

Benutzerdefinierte Module

Benutzerdefinierte Tabellen injizieren:

runner.run({
    source = [[
        return sdk.version
    ]],
    custom_modules = {
        sdk = {version = "1.0.0", api_key = "xxx"}
    }
})

Kontextwerte

Daten übergeben, die als ctx zugänglich sind:

runner.run({
    source = [[
        return "Hello, " .. ctx.user
    ]],
    context = {user = "Alice"}
})

Programme kompilieren

Einmal kompilieren für wiederholte Ausführung:

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

Sicherheitsmodell

Modulklassen

Module werden nach Fähigkeiten kategorisiert:

Klasse Beschreibung Standard
deterministic Reine Funktionen Erlaubt
encoding Datenkodierung Erlaubt
time Zeitoperationen Erlaubt
nondeterministic Zufall, etc. Erlaubt
process Spawn, Registry Blockiert
storage Datei, Datenbank Blockiert
network HTTP, Sockets Blockiert

Blockierte Klassen aktivieren

runner.run({
    source = [[
        local http = require("http_client")
        return http.get("https://api.example.com")
    ]],
    modules = {"http_client"},
    allow_classes = {"network"}
})

Berechtigungsprüfungen

Das System prüft Berechtigungen für:

  • eval.compile - Vor Kompilierung
  • eval.run - Vor Ausführung
  • eval.module - Für jedes Modul in Whitelist
  • eval.import - Für jeden Registry-Import
  • eval.class - Für jede erlaubte Klasse

In Sicherheitsrichtlinien konfigurieren.

Fehlerbehandlung

local result, err = runner.run({...})
if err then
    if err:kind() == errors.PERMISSION_DENIED then
        -- Zugriff durch Sicherheitsrichtlinie verweigert
    elseif err:kind() == errors.INVALID then
        -- Ungültige Quelle oder Konfiguration
    elseif err:kind() == errors.INTERNAL then
        -- Ausführungs- oder Kompilierungsfehler
    end
end

Anwendungsfälle

Plugin-System

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

Template-Auswertung

local template = "Hello, {{name}}! You have {{count}} messages."
local compiled = expr.compile("name")

-- Schnelle wiederholte Auswertung
for _, user in ipairs(users) do
    local greeting = compiled:run({name = user.name})
end

Benutzerskripte

local user_code = request:body()

local result, err = runner.run({
    source = user_code,
    modules = {"json", "text"},  -- Nur sichere Module
    context = {data = input_data}
})

Siehe auch