From 3ae9c8ff159b12fe829d401204473f11bfc41347 Mon Sep 17 00:00:00 2001 From: Saurabh Pandey Date: Sat, 19 Aug 2023 20:11:23 +0530 Subject: [PATCH] Add nil check for knowledge base inside execute and fetch matching rule passing a nil knowledgebase to exceute or fetch matching rules method was resulting in a panic which took some time to find, adding a nil check for both of these methods to throw proper error in this case added unit tests for the same --- engine/GruleEngine.go | 19 +++++++-- examples/NilKnowledgeBasePanic_test.go | 54 ++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 examples/NilKnowledgeBasePanic_test.go diff --git a/engine/GruleEngine.go b/engine/GruleEngine.go index 2685c840..3d77ffd1 100755 --- a/engine/GruleEngine.go +++ b/engine/GruleEngine.go @@ -17,11 +17,12 @@ package engine import ( "context" "fmt" - "github.com/sirupsen/logrus" - "go.uber.org/zap" "sort" "time" + "github.com/sirupsen/logrus" + "go.uber.org/zap" + "github.com/hyperjumptech/grule-rule-engine/ast" "github.com/hyperjumptech/grule-rule-engine/logger" ) @@ -85,7 +86,6 @@ type GruleEngine struct { // Execute function is the same as ExecuteWithContext(context.Background()) func (g *GruleEngine) Execute(dataCtx ast.IDataContext, knowledge *ast.KnowledgeBase) error { - return g.ExecuteWithContext(context.Background(), dataCtx, knowledge) } @@ -120,6 +120,12 @@ func (g *GruleEngine) notifyBeginCycle(cycle uint64) { // The engine will evaluate context cancelation status in each cycle. // The engine also do conflict resolution of which rule to execute. func (g *GruleEngine) ExecuteWithContext(ctx context.Context, dataCtx ast.IDataContext, knowledge *ast.KnowledgeBase) error { + // check for nil values of knowledgebase before trying to execute rule + if knowledge == nil { + log.Warn("cannot execute rules from a nil knowledgebase") + return fmt.Errorf("cannot execute rules from a nil knowledgebase") + } + log.Debugf("Starting rule execution using knowledge '%s' version %s. Contains %d rule entries", knowledge.Name, knowledge.Version, len(knowledge.RuleEntries)) // Prepare the timer, we need to measure the processing time in debug mode. @@ -241,7 +247,14 @@ func (g *GruleEngine) ExecuteWithContext(ctx context.Context, dataCtx ast.IDataC // FetchMatchingRules function is responsible to fetch all the rules that matches to a fact against all rule entries // Returns []*ast.RuleEntry order by salience func (g *GruleEngine) FetchMatchingRules(dataCtx ast.IDataContext, knowledge *ast.KnowledgeBase) ([]*ast.RuleEntry, error) { + // check for nil values of knowledgebase before trying to access its members + if knowledge == nil { + log.Warn("cannot fetch rules from a nil knowledgebase") + return nil, fmt.Errorf("cannot fetch rules from a nil knowledgebase") + } + log.Debugf("Starting rule matching using knowledge '%s' version %s. Contains %d rule entries", knowledge.Name, knowledge.Version, len(knowledge.RuleEntries)) + // Prepare the build-in function and add to datacontext. defunc := &ast.BuiltInFunctions{ Knowledge: knowledge, diff --git a/examples/NilKnowledgeBasePanic_test.go b/examples/NilKnowledgeBasePanic_test.go new file mode 100644 index 00000000..aaf16de6 --- /dev/null +++ b/examples/NilKnowledgeBasePanic_test.go @@ -0,0 +1,54 @@ +// Copyright hyperjumptech/grule-rule-engine Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package examples + +import ( + "testing" + + "grule-rule-engine/ast" + "grule-rule-engine/engine" + + "github.com/stretchr/testify/assert" +) + +func Test_NoPanicOnEmptyKnowledgeBase(t *testing.T) { + // create a new fact for user + user := &User{ + Name: "Calo", + Age: 0, + Male: true, + } + // create an empty data context + dataContext := ast.NewDataContext() + // add the fact struct to the data context + err := dataContext.Add("User", user) + if err != nil { + t.Fatal(err) + } + + t.Run("with nil knowledge base in execute", func(t *testing.T) { + eng := &engine.GruleEngine{MaxCycle: 10} + err = eng.Execute(dataContext, nil) + + assert.ErrorContains(t, err, "cannot execute rules from a nil knowledgebase") + }) + + t.Run("with nil knowledge base in FetchMatchingRules", func(t *testing.T) { + eng := &engine.GruleEngine{MaxCycle: 10} + _, err = eng.FetchMatchingRules(dataContext, nil) + + assert.ErrorContains(t, err, "cannot fetch rules from a nil knowledgebase") + }) +}