Skip to content

Commit

Permalink
Feat/editor (#343)
Browse files Browse the repository at this point in the history
* Include fix #339
* Includes some code for the playground yet it was not yet fixed.
* Increased go version to 1.18
  • Loading branch information
newm4n authored Dec 15, 2022
1 parent b4475ad commit 2134fd5
Show file tree
Hide file tree
Showing 61 changed files with 59,534 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-test-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.18
- name: Checkout code
uses: actions/checkout@v2
- name: Fetching dependencies
Expand Down
11 changes: 11 additions & 0 deletions ast/DataContext.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ type DataContext struct {
complete bool
}

func (ctx *DataContext) GetKeys() []string {
ret := make([]string, len(ctx.ObjectStore))
c := 0
for k, _ := range ctx.ObjectStore {
ret[c] = k
c++
}
return ret
}

// Complete marks the DataContext as completed, telling the engine to stop processing rules
func (ctx *DataContext) Complete() {
ctx.complete = true
Expand All @@ -59,6 +69,7 @@ type IDataContext interface {
Add(key string, obj interface{}) error
AddJSON(key string, JSON []byte) error
Get(key string) model.ValueNode
GetKeys() []string

Retract(key string)
IsRetracted(key string) bool
Expand Down
4 changes: 0 additions & 4 deletions ast/RuleEntry.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,6 @@ func (e *RuleEntry) Clone(cloneTable *pkg.CloneTable) *RuleEntry {
}
}

if e.GetSnapshot() != clone.GetSnapshot() {
panic(fmt.Sprintf("RuleEntry clone failed : \noriginal [%s] \nclone [%s]", e.GetSnapshot(), clone.GetSnapshot()))
}

return clone
}

Expand Down
21 changes: 21 additions & 0 deletions builder/RuleBuilder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,24 @@ func TestNoPanic(t *testing.T) {
err := ruleBuilder.BuildRuleFromResource("CallingLog", "0.1.1", pkg.NewBytesResource([]byte(GRL)))
assert.NoError(t, err)
}

func TestRuleEntry_Clone(t *testing.T) {
testRule := `rule CloneRule "Duplicate Rule 1" salience 5 {
when
(Fact.Distance > 5000 && Fact.Duration > 120) && (Fact.Result == false)
Then
Fact.NetAmount=143.320007;
Fact.Result=true;
}`
lib := ast.NewKnowledgeLibrary()
rb := NewRuleBuilder(lib)
err := rb.BuildRuleFromResource("testrule", "0.1.1", pkg.NewBytesResource([]byte(testRule)))
assert.NoError(t, err)
kb := lib.GetKnowledgeBase("testrule", "0.1.1")
re := kb.RuleEntries["CloneRule"]

ct := &pkg.CloneTable{Records: make(map[string]*pkg.CloneRecord)}
reClone := re.Clone(ct)

assert.Equal(t, re.GetSnapshot(), reClone.GetSnapshot())
}
101 changes: 101 additions & 0 deletions editor/EvaluationRoute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package editor

import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/hyperjumptech/grule-rule-engine/ast"
"github.com/hyperjumptech/grule-rule-engine/builder"
"github.com/hyperjumptech/grule-rule-engine/engine"
"github.com/hyperjumptech/grule-rule-engine/pkg"
mux "github.com/hyperjumptech/hyper-mux"
"io/ioutil"
"net/http"
)

type JSONData struct {
Name string
JSONData string `json:"jsonText"`
}

type EvaluateRequest struct {
GrlText string `json:"grlText"`
Input []*JSONData `json:"jsonInput"`
}

func InitializeEvaluationRoute(router *mux.HyperMux) {
router.AddRoute("/evaluate", http.MethodPost, func(w http.ResponseWriter, r *http.Request) {
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(fmt.Sprintf("error while reading body stream. got %v", err)))
return
}
evReq := &EvaluateRequest{}
err = json.Unmarshal(bodyBytes, evReq)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(fmt.Sprintf("wrong json format. got %v \n\n Json : %s", err, string(bodyBytes))))
return
}

dataContext := ast.NewDataContext()

for _, jd := range evReq.Input {
jsonByte, err := base64.StdEncoding.DecodeString(jd.JSONData)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(fmt.Sprintf("json data named %s should be sent using base64. got %v", jd.Name, err)))
return
}
err = dataContext.AddJSON(jd.Name, jsonByte)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(fmt.Sprintf("invalid JSON data named %s when add json to context got %v", jd.Name, err)))
return
}
}

lib := ast.NewKnowledgeLibrary()
rb := builder.NewRuleBuilder(lib)

grlByte, err := base64.StdEncoding.DecodeString(evReq.GrlText)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(fmt.Sprintf("GRL data should be sent using base64. got %v", err)))
return
}

err = rb.BuildRuleFromResource("Evaluator", "0.0.1", pkg.NewBytesResource(grlByte))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(fmt.Sprintf("invalid GRL : %s", err.Error())))
return
}

eng1 := &engine.GruleEngine{MaxCycle: 5}
kb := lib.NewKnowledgeBaseInstance("Evaluator", "0.0.1")
err = eng1.Execute(dataContext, kb)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte(fmt.Sprintf("Grule Error : %s", err.Error())))
return
}

respData := make(map[string]interface{})
for _, keyName := range dataContext.GetKeys() {
respData[keyName] = dataContext.Get(keyName)
}

resultBytes, err := json.Marshal(respData)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(fmt.Sprintf("Error marshaling result : %s", err.Error())))
return
}

w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(resultBytes)
})
}
65 changes: 65 additions & 0 deletions editor/Server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package editor

import (
"context"
mux "github.com/hyperjumptech/hyper-mux"
"github.com/sirupsen/logrus"
"log"
"net/http"
"os"
"os/signal"
"time"
)

var (
hmux = mux.NewHyperMux()
)

func Start() {
InitializeStaticRoute(hmux)
InitializeEvaluationRoute(hmux)

var wait time.Duration
address := "0.0.0.0:32123"

logrus.Infof("Starting Editor at http://%s", address)

srv := &http.Server{
Addr: address,
WriteTimeout: 10 * time.Second,
ReadTimeout: 10 * time.Second,
IdleTimeout: 10 * time.Second,
Handler: hmux, // Pass our instance of gorilla/mux in.
}

startTime := time.Now()

// Run our server in a goroutine so that it doesn't block.
go func() {
if err := srv.ListenAndServe(); err != nil {
log.Println(err)
}
}()

c := make(chan os.Signal, 1)
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
signal.Notify(c, os.Interrupt)

// Block until we receive our signal.
<-c

// Create a deadline to wait for.
ctx, cancel := context.WithTimeout(context.Background(), wait)
defer cancel()
// Doesn't block if no connections, but will otherwise wait
// until the timeout deadline.
srv.Shutdown(ctx)
// Optionally, you could run srv.Shutdown in a goroutine and block on
// <-ctx.Done() if your application should wait for other services
// to finalize based on context cancellation.
dur := time.Now().Sub(startTime)
logrus.Infof("Shutting down. This Satpam been protecting the world for %s", dur.String())
os.Exit(0)

}
105 changes: 105 additions & 0 deletions editor/StaticRoute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package editor

import (
"embed"
"fmt"
"github.com/hyperjumptech/grule-rule-engine/editor/mime"
mux "github.com/hyperjumptech/hyper-mux"
"github.com/sirupsen/logrus"
"net/http"
"os"
"strings"
)

//go:embed statics
var fs embed.FS

var (
errFileNotFound = fmt.Errorf("file not found")
)

type FileData struct {
Bytes []byte
ContentType string
}

func IsDir(path string) bool {
for _, s := range GetPathTree("static") {
if s == "[DIR]"+path {
return true
}
}
return false
}

func GetPathTree(path string) []string {
logrus.Debugf("Into %s", path)
var entries []os.DirEntry
var err error
if strings.HasPrefix(path, "./") {
entries, err = fs.ReadDir(path[2:])
} else {
entries, err = fs.ReadDir(path)
}
ret := make([]string, 0)
if err != nil {
return ret
}
logrus.Debugf("Path %s %d etries", path, len(entries))
for _, e := range entries {
if e.IsDir() {
ret = append(ret, "[DIR]"+path+"/"+e.Name())
ret = append(ret, GetPathTree(path+"/"+e.Name())...)
} else {
ret = append(ret, path+"/"+e.Name())
}
}
return ret
}

func GetFile(path string) (*FileData, error) {
bytes, err := fs.ReadFile(path)
if err != nil {
return nil, err
}
mimeType, err := mime.MimeForFileName(path)
if err != nil {
return &FileData{
Bytes: bytes,
ContentType: http.DetectContentType(bytes),
}, nil
}
return &FileData{
Bytes: bytes,
ContentType: mimeType,
}, nil
}

func InitializeStaticRoute(router *mux.HyperMux) {
for _, p := range GetPathTree("statics") {
if !strings.HasPrefix(p, "[DIR]") {
path := p[len("statics"):]
router.AddRoute(path, http.MethodGet, StaticHandler(p))
}
}

router.AddRoute("/", http.MethodGet, func(writer http.ResponseWriter, request *http.Request) {
writer.Header().Add("Location", "/index.html")
writer.WriteHeader(http.StatusMovedPermanently)
})
}

func StaticHandler(path string) func(writer http.ResponseWriter, request *http.Request) {
fData, err := GetFile(path)
if err != nil {
return func(writer http.ResponseWriter, request *http.Request) {
_, _ = writer.Write([]byte(err.Error()))
writer.WriteHeader(http.StatusInternalServerError)
}
}
return func(writer http.ResponseWriter, request *http.Request) {
writer.Header().Add("Content-Type", fData.ContentType)
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write(fData.Bytes)
}
}
11 changes: 11 additions & 0 deletions editor/cmd/Main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
"github.com/hyperjumptech/grule-rule-engine/editor"
"github.com/sirupsen/logrus"
)

func main() {
logrus.SetLevel(logrus.TraceLevel)
editor.Start()
}
Loading

0 comments on commit 2134fd5

Please sign in to comment.