Files
Dušan Borovčanin 61d0427898 NOISSUE - Rename to Magistrala (#3427)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-04-06 15:23:42 +02:00

186 lines
4.9 KiB
Go

// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package re
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"github.com/absmach/magistrala/pkg/errors"
pkglog "github.com/absmach/magistrala/pkg/logger"
"github.com/absmach/magistrala/pkg/messaging"
"github.com/vadv/gopher-lua-libs/argparse"
"github.com/vadv/gopher-lua-libs/base64"
bit "github.com/vadv/gopher-lua-libs/bit"
"github.com/vadv/gopher-lua-libs/crypto"
"github.com/vadv/gopher-lua-libs/db"
"github.com/vadv/gopher-lua-libs/filepath"
client "github.com/vadv/gopher-lua-libs/http/client"
"github.com/vadv/gopher-lua-libs/ioutil"
luajson "github.com/vadv/gopher-lua-libs/json"
"github.com/vadv/gopher-lua-libs/regexp"
"github.com/vadv/gopher-lua-libs/storage"
"github.com/vadv/gopher-lua-libs/strings"
luatime "github.com/vadv/gopher-lua-libs/time"
"github.com/vadv/gopher-lua-libs/yaml"
lua "github.com/yuin/gopher-lua"
)
const payloadKey = "payload"
func (re *re) processLua(ctx context.Context, details []slog.Attr, r Rule, msg *messaging.Message) pkglog.RunInfo {
l := lua.NewState()
defer l.Close()
preload(l)
message := prepareMsg(l, msg)
// Set the message object as a Lua global variable.
l.SetGlobal("message", message)
if err := l.DoString(r.Logic.Value); err != nil {
return pkglog.RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to run rule logic: %s", err), Details: details}
}
// Get the last result.
result := l.Get(-1)
if result == lua.LNil {
return pkglog.RunInfo{Level: slog.LevelWarn, Message: "rule with nil script result", Details: details}
}
// Converting Lua is an expensive operation, so
// don't do it if there are no outputs.
if len(r.Outputs) == 0 {
return pkglog.RunInfo{Level: slog.LevelWarn, Message: "rule with no outputs", Details: details}
}
var err error
res := convertLua(result)
for _, o := range r.Outputs {
// If value is false, don't run the follow-up.
if v, ok := res.(bool); ok && !v {
return pkglog.RunInfo{Level: slog.LevelInfo, Message: "logic returned false", Details: details}
}
if e := re.handleOutput(ctx, o, r, msg, res); e != nil {
err = errors.Wrap(e, err)
}
}
ret := pkglog.RunInfo{Level: slog.LevelInfo, Message: "rule processed successfully", Details: details}
if err != nil {
ret.Level = slog.LevelError
ret.Message = fmt.Sprintf("failed to handle rule output: %s", err)
}
return ret
}
func preload(l *lua.LState) {
db.Preload(l)
ioutil.Preload(l)
luajson.Preload(l)
yaml.Preload(l)
crypto.Preload(l)
regexp.Preload(l)
luatime.Preload(l)
storage.Preload(l)
base64.Preload(l)
argparse.Preload(l)
strings.Preload(l)
filepath.Preload(l)
client.Preload(l)
bit.Preload(l)
}
func prepareMsg(l *lua.LState, msg *messaging.Message) lua.LValue {
message := l.NewTable()
message.RawSetString("domain", lua.LString(msg.Domain))
message.RawSetString("channel", lua.LString(msg.Channel))
message.RawSetString("subtopic", lua.LString(msg.Subtopic))
message.RawSetString("client_id", lua.LString(msg.ClientIdentity()))
message.RawSetString("publisher", lua.LString(msg.Publisher))
message.RawSetString("protocol", lua.LString(msg.Protocol))
message.RawSetString("created", lua.LNumber(msg.Created))
var payload any
if err := json.Unmarshal(msg.GetPayload(), &payload); err != nil {
pld := l.NewTable()
// If message is not JSON, set binary payload and exit.
for i, b := range msg.Payload {
// Lua tables are 1-indexed.
pld.Insert(i+1, lua.LNumber(b))
}
message.RawSetString(payloadKey, pld)
return message
}
// Payload is JSON, set the correct value.
message.RawSetString(payloadKey, traverseJson(l, payload))
return message
}
func traverseJson(l *lua.LState, value any) lua.LValue {
switch val := value.(type) {
case string:
return lua.LString(val)
case float64:
return lua.LNumber(val)
case int:
return lua.LNumber(float64(val))
case json.Number:
if num, err := val.Float64(); err != nil {
return lua.LNumber(num)
}
return lua.LNil
case bool:
return lua.LBool(val)
case []any:
t := l.NewTable()
for i, j := range val {
t.RawSetInt(i+1, traverseJson(l, j))
}
return t
case map[string]any:
t := l.NewTable()
for k, v := range val {
t.RawSetString(k, traverseJson(l, v))
}
return t
default:
return lua.LNil
}
}
func convertLua(lv lua.LValue) any {
switch v := lv.(type) {
case *lua.LTable:
isArray := true
v.ForEach(func(key, value lua.LValue) {
if key.Type() != lua.LTNumber {
isArray = false
}
})
if isArray {
arr := []any{}
v.ForEach(func(key, value lua.LValue) {
arr = append(arr, convertLua(value))
})
return arr
}
obj := map[string]any{}
v.ForEach(func(key, value lua.LValue) {
obj[key.String()] = convertLua(value)
})
return obj
case lua.LString:
return string(v)
case lua.LNumber:
return float64(v)
case lua.LBool:
return bool(v)
case *lua.LNilType:
return nil
default:
return v.String()
}
}