From 3765de357e2a425019595a90159cae7f2ea9014b Mon Sep 17 00:00:00 2001 From: Ferdinand Neman Date: Mon, 28 Aug 2023 17:08:46 +0700 Subject: [PATCH] Chore/panic (#398) * Removed panic in JSON Resouce handling code. Some method signature impacted * removed all panic * Removed panic on json tools related * Removed panic from Working Memory * Removed panic from Working Memory part 2 * Removed panic from JsonDom.go * Worked trying to remove all bad panic --------- Co-authored-by: Ferdinand Neman --- ast/ExpressionAtom.go | 2 +- ast/KnowledgeBase.go | 31 +-- ast/RuleEntry.go | 3 +- ast/Serializer.go | 8 +- ast/WorkingMemory.go | 20 +- examples/Issue328_test.go | 2 - pkg/JsonResource.go | 271 +++++++++++++++----------- pkg/JsonResource_test.go | 121 +++--------- pkg/embeddedResource.go | 5 +- pkg/jsontool/JsonDom.go | 366 ++++++++++++++++++++++++----------- pkg/jsontool/JsonDom_test.go | 206 ++++++++++++++------ pkg/resource.go | 4 +- 12 files changed, 631 insertions(+), 408 deletions(-) diff --git a/ast/ExpressionAtom.go b/ast/ExpressionAtom.go index a5413bfb..6cdac555 100755 --- a/ast/ExpressionAtom.go +++ b/ast/ExpressionAtom.go @@ -413,5 +413,5 @@ func (e *ExpressionAtom) Evaluate(dataContext IDataContext, memory *WorkingMemor return e.Value, nil } - panic("should not be reached") + return reflect.Value{}, fmt.Errorf("this portion of code should not be reached") } diff --git a/ast/KnowledgeBase.go b/ast/KnowledgeBase.go index 160a86bf..5ba47de3 100755 --- a/ast/KnowledgeBase.go +++ b/ast/KnowledgeBase.go @@ -18,7 +18,6 @@ import ( "bytes" "fmt" "github.com/google/uuid" - "github.com/sirupsen/logrus" "io" "sort" "strings" @@ -84,7 +83,6 @@ func (lib *KnowledgeLibrary) LoadKnowledgeBaseFromReader(reader io.Reader, overw if r := recover(); r != nil { retKb = nil retErr = fmt.Errorf("panic recovered during LoadKnowledgeBaseFromReader, recover \"%v\". send us your report to https://github.com/hyperjumptech/grule-rule-engine/issues", r) - logrus.Panicf("panic recovered during LoadKnowledgeBaseFromReader, recover \"%v\". send us your report to https://github.com/hyperjumptech/grule-rule-engine/issues", r) } }() @@ -94,7 +92,10 @@ func (lib *KnowledgeLibrary) LoadKnowledgeBaseFromReader(reader io.Reader, overw return nil, err } - knowledgeBase := catalog.BuildKnowledgeBase() + knowledgeBase, err := catalog.BuildKnowledgeBase() + if err != nil { + return nil, err + } if overwrite { lib.Library[fmt.Sprintf("%s:%s", knowledgeBase.Name, knowledgeBase.Version)] = knowledgeBase @@ -127,21 +128,25 @@ func (lib *KnowledgeLibrary) StoreKnowledgeBaseToWriter(writer io.Writer, name, // NewKnowledgeBaseInstance will create a new instance based on KnowledgeBase blue print // identified by its name and version -func (lib *KnowledgeLibrary) NewKnowledgeBaseInstance(name, version string) *KnowledgeBase { +func (lib *KnowledgeLibrary) NewKnowledgeBaseInstance(name, version string) (*KnowledgeBase, error) { knowledgeBase, ok := lib.Library[fmt.Sprintf("%s:%s", name, version)] if ok { - newClone := knowledgeBase.Clone(pkg.NewCloneTable()) + newClone, err := knowledgeBase.Clone(pkg.NewCloneTable()) + if err != nil { + return nil, err + } if knowledgeBase.IsIdentical(newClone) { AstLog.Debugf("Successfully create instance [%s:%s]", newClone.Name, newClone.Version) - return newClone + return newClone, nil } AstLog.Fatalf("ORIGIN : %s", knowledgeBase.GetSnapshot()) AstLog.Fatalf("CLONE : %s", newClone.GetSnapshot()) - panic("The clone is not identical") + + return nil, fmt.Errorf("the clone is not identical") } - return nil + return nil, fmt.Errorf("specified knowledge base name and version not exist") } // KnowledgeBase is a collection of RuleEntries. It has a name and version. @@ -210,7 +215,7 @@ func (e *KnowledgeBase) GetSnapshot() string { } // Clone will clone this instance of KnowledgeBase and produce another (structure wise) identical instance. -func (e *KnowledgeBase) Clone(cloneTable *pkg.CloneTable) *KnowledgeBase { +func (e *KnowledgeBase) Clone(cloneTable *pkg.CloneTable) (*KnowledgeBase, error) { clone := &KnowledgeBase{ Name: e.Name, Version: e.Version, @@ -228,10 +233,14 @@ func (e *KnowledgeBase) Clone(cloneTable *pkg.CloneTable) *KnowledgeBase { } } if e.WorkingMemory != nil { - clone.WorkingMemory = e.WorkingMemory.Clone(cloneTable) + wm, err := e.WorkingMemory.Clone(cloneTable) + if err != nil { + return nil, err + } + clone.WorkingMemory = wm } - return clone + return clone, nil } // AddRuleEntry add ruleentry into this knowledge base. diff --git a/ast/RuleEntry.go b/ast/RuleEntry.go index 87512f97..0e5e5184 100755 --- a/ast/RuleEntry.go +++ b/ast/RuleEntry.go @@ -171,7 +171,8 @@ func (e *RuleEntry) Evaluate(ctx context.Context, dataContext IDataContext, memo } defer func() { if r := recover(); r != nil { - err = fmt.Errorf("Error while evaluating rule %s, panic recovered", e.RuleName) + err = fmt.Errorf("error while evaluating rule %s, panic recovered", e.RuleName) + can = false } }() if e.Retracted { diff --git a/ast/Serializer.go b/ast/Serializer.go index fcacbe38..831bbac1 100755 --- a/ast/Serializer.go +++ b/ast/Serializer.go @@ -101,7 +101,7 @@ type Catalog struct { // BuildKnowledgeBase will rebuild a knowledgebase from this Catalog. // the rebuilt KnowledgeBase is identical to the original KnowledgeBase from // which this Catalog was built. -func (cat *Catalog) BuildKnowledgeBase() *KnowledgeBase { +func (cat *Catalog) BuildKnowledgeBase() (*KnowledgeBase, error) { workingMem := &WorkingMemory{ Name: cat.MemoryName, Version: cat.MemoryVersion, @@ -280,7 +280,7 @@ func (cat *Catalog) BuildKnowledgeBase() *KnowledgeBase { } importTable[amet.AstID] = n default: - panic("Unrecognized meta type") + return nil, fmt.Errorf("unrecognized meta type") } } @@ -407,7 +407,7 @@ func (cat *Catalog) BuildKnowledgeBase() *KnowledgeBase { whenScope.Expression = importTable[amet.ExpressionID].(*Expression) } default: - panic("Unrecognized meta type") + return nil, fmt.Errorf("unknown AST type") } } @@ -450,7 +450,7 @@ func (cat *Catalog) BuildKnowledgeBase() *KnowledgeBase { } } - return knowledgeBase + return knowledgeBase, nil } // Equals used for testing purpose, to ensure that two catalog diff --git a/ast/WorkingMemory.go b/ast/WorkingMemory.go index 46b81078..00945a32 100755 --- a/ast/WorkingMemory.go +++ b/ast/WorkingMemory.go @@ -143,7 +143,7 @@ func (workingMem *WorkingMemory) Equals(that *WorkingMemory) bool { } // Clone will clone this WorkingMemory. The new clone will have an identical structure -func (workingMem *WorkingMemory) Clone(cloneTable *pkg.CloneTable) *WorkingMemory { +func (workingMem *WorkingMemory) Clone(cloneTable *pkg.CloneTable) (*WorkingMemory, error) { AstLog.Debugf("Cloning working memory %s:%s", workingMem.Name, workingMem.Version) clone := NewWorkingMemory(workingMem.Name, workingMem.Version) @@ -154,7 +154,7 @@ func (workingMem *WorkingMemory) Clone(cloneTable *pkg.CloneTable) *WorkingMemor clone.expressionSnapshotMap[k] = cloneTable.Records[expr.AstID].CloneInstance.(*Expression) } else { - panic(fmt.Sprintf("expression %s is not on the clone table - %s", expr.GrlText, expr.GetSnapshot())) + return nil, fmt.Errorf("expression %s is not on the clone table - %s", expr.GrlText, expr.GetSnapshot()) } } } @@ -166,7 +166,7 @@ func (workingMem *WorkingMemory) Clone(cloneTable *pkg.CloneTable) *WorkingMemor clone.expressionAtomSnapshotMap[k] = cloneTable.Records[exprAtm.AstID].CloneInstance.(*ExpressionAtom) } else { - panic(fmt.Sprintf("expression atom %s is not on the clone table. ASTID %s", exprAtm.GrlText, exprAtm.AstID)) + return nil, fmt.Errorf("expression atom %s is not on the clone table. ASTID %s", exprAtm.GrlText, exprAtm.AstID) } } } @@ -178,7 +178,7 @@ func (workingMem *WorkingMemory) Clone(cloneTable *pkg.CloneTable) *WorkingMemor clone.variableSnapshotMap[key] = cloneTable.Records[variable.AstID].CloneInstance.(*Variable) } else { - panic(fmt.Sprintf("variable %s is not on the clone table", variable.GrlText)) + return nil, fmt.Errorf("variable %s is not on the clone table", variable.GrlText) } } } @@ -194,12 +194,12 @@ func (workingMem *WorkingMemory) Clone(cloneTable *pkg.CloneTable) *WorkingMemor clone.expressionVariableMap[clonedVari][k2] = cloneTable.Records[expr.AstID].CloneInstance.(*Expression) } else { - panic(fmt.Sprintf("expression %s is not on the clone table", expr.GrlText)) + return nil, fmt.Errorf("expression %s is not on the clone table", expr.GrlText) } } } else { - panic(fmt.Sprintf("variable %s is not on the clone table", key.GrlText)) + return nil, fmt.Errorf("variable %s is not on the clone table", key.GrlText) } } } @@ -215,12 +215,12 @@ func (workingMem *WorkingMemory) Clone(cloneTable *pkg.CloneTable) *WorkingMemor clone.expressionAtomVariableMap[clonedVari][k2] = cloneTable.Records[expr.AstID].CloneInstance.(*ExpressionAtom) } else { - panic(fmt.Sprintf("expression atom %s is not on the clone table", expr.GrlText)) + return nil, fmt.Errorf("expression atom %s is not on the clone table", expr.GrlText) } } } else { - panic(fmt.Sprintf("variable %s is not on the clone table", key.GrlText)) + return nil, fmt.Errorf("variable %s is not on the clone table", key.GrlText) } } } @@ -228,10 +228,10 @@ func (workingMem *WorkingMemory) Clone(cloneTable *pkg.CloneTable) *WorkingMemor if workingMem.Equals(clone) { clone.DebugContent() - return clone + return clone, nil } - panic("Clone not equals the origin.") + return nil, fmt.Errorf("clone not equals the origin") } // IndexVariables will index all expression and expression atoms that contains a speciffic variable name diff --git a/examples/Issue328_test.go b/examples/Issue328_test.go index 1f695e5b..ee0cf187 100644 --- a/examples/Issue328_test.go +++ b/examples/Issue328_test.go @@ -40,13 +40,11 @@ func TestMethodCall_SliceOOR(t *testing.T) { err = rb.BuildRuleFromResource("Test", "0.1.1", pkg.NewBytesResource([]byte(SliceOORRule))) assert.NoError(t, err) - // expect no panic and no error (ReturnErrOnFailedRuleEvaluation = false) eng1 := &engine.GruleEngine{MaxCycle: 5} kb := lib.NewKnowledgeBaseInstance("Test", "0.1.1") err = eng1.Execute(dataContext, kb) assert.NoError(t, err) - // expect no panic and execute to return an error here eng1 = &engine.GruleEngine{MaxCycle: 5, ReturnErrOnFailedRuleEvaluation: true} kb = lib.NewKnowledgeBaseInstance("Test", "0.1.1") err = eng1.Execute(dataContext, kb) diff --git a/pkg/JsonResource.go b/pkg/JsonResource.go index 2e9f6389..94e08e7b 100644 --- a/pkg/JsonResource.go +++ b/pkg/JsonResource.go @@ -43,15 +43,15 @@ type JSONResourceBundle struct { } // NewJSONResourceFromResource innstantiates a new JSON resource parser from an underlying Resource. -func NewJSONResourceFromResource(res Resource) Resource { +func NewJSONResourceFromResource(res Resource) (Resource, error) { if _, ok := res.(*JSONResource); ok { - panic("cannot create JSON resource from JSON resource") + return nil, fmt.Errorf("cannot create JSON resource from JSON resource") } return &JSONResource{ subRes: res, - } + }, nil } // Load will load the underlying Resource and parse the JSON rules into standard GRule syntax. @@ -87,14 +87,14 @@ func (jr *JSONResource) String() string { } // NewJSONResourceBundleFromBundle innstantiates a new bundled JSON resource parser from an underlying ResourceBundle. -func NewJSONResourceBundleFromBundle(bundle ResourceBundle) ResourceBundle { +func NewJSONResourceBundleFromBundle(bundle ResourceBundle) (ResourceBundle, error) { if _, ok := bundle.(*JSONResourceBundle); ok { - panic("cannot create JSON resource bundle from JSON resource bundle") + return nil, fmt.Errorf("cannot create JSON resource bundle from JSON resource bundle") } return &JSONResourceBundle{ subRes: bundle, - } + }, nil } // Load will load the underlying ResourceBundle and parse the JSON rules into standard GRule syntax. @@ -106,7 +106,10 @@ func (jrb *JSONResourceBundle) Load() ([]Resource, error) { } nress := make([]Resource, len(ress)) for i := 0; i < len(ress); i++ { - nress[i] = NewJSONResourceFromResource(ress[i]) + nress[i], err = NewJSONResourceFromResource(ress[i]) + if err != nil { + return nil, err + } } return nress, nil @@ -116,8 +119,12 @@ func (jrb *JSONResourceBundle) Load() ([]Resource, error) { func (jrb *JSONResourceBundle) MustLoad() []Resource { ress := jrb.subRes.MustLoad() nress := make([]Resource, len(ress)) + var err error for i := 0; i < len(ress); i++ { - nress[i] = NewJSONResourceFromResource(ress[i]) + nress[i], err = NewJSONResourceFromResource(ress[i]) + if err != nil { + panic(err.Error()) + } } return nress @@ -125,11 +132,6 @@ func (jrb *JSONResourceBundle) MustLoad() []Resource { // ParseJSONRuleset accepts a byte array containing an array of rules in JSON format to be parsed into GRule syntax. func ParseJSONRuleset(data []byte) (rs string, err error) { - defer func() { - if x := recover(); x != nil { - err = fmt.Errorf("%v", x) - } - }() var rules []GruleJSON err = json.Unmarshal(data, &rules) if err != nil { @@ -138,7 +140,11 @@ func ParseJSONRuleset(data []byte) (rs string, err error) { } var sb strings.Builder for i := 0; i < len(rules); i++ { - sb.WriteString(parseRule(&rules[i])) + rulestr, err := parseRule(&rules[i]) + if err != nil { + return "", err + } + sb.WriteString(rulestr) } rs = sb.String() @@ -147,11 +153,6 @@ func ParseJSONRuleset(data []byte) (rs string, err error) { // ParseJSONRule accepts a byte array containing an rule in JSON format to be parsed into GRule syntax. func ParseJSONRule(data []byte) (rs string, err error) { - defer func() { - if x := recover(); x != nil { - err = fmt.Errorf("%v", x) - } - }() var rule GruleJSON err = json.Unmarshal(data, &rule) if err != nil { @@ -159,35 +160,30 @@ func ParseJSONRule(data []byte) (rs string, err error) { return } - rs = parseRule(&rule) + rs, err = parseRule(&rule) return } // ParseRule Accepts a struct of GruleJSON rule and returns the parsed string of GRule. func ParseRule(rule *GruleJSON) (r string, err error) { - defer func() { - if x := recover(); x != nil { - err = fmt.Errorf("%v", x) - } - }() - r = parseRule(rule) + r, err = parseRule(rule) return } -func parseRule(rule *GruleJSON) string { +func parseRule(rule *GruleJSON) (string, error) { if len(rule.Name) == 0 { - panic("rule name cannot be blank") + return "", fmt.Errorf("rule name cannot be blank") } if rule.When == nil { - panic("rule when condition cannot be nil") + return "", fmt.Errorf("rule when condition cannot be nil") } if rule.Then == nil { - panic("rule thenn condition cannot be nil") + return "", fmt.Errorf("rule thenn condition cannot be nil") } var stringBuilder strings.Builder stringBuilder.WriteString("rule ") @@ -197,9 +193,17 @@ func parseRule(rule *GruleJSON) string { stringBuilder.WriteString(" salience ") stringBuilder.WriteString(strconv.Itoa(rule.Salience)) stringBuilder.WriteString(" {\n when\n ") - stringBuilder.WriteString(parseWhen(rule.When)) + when, err := parseWhen(rule.When) + if err != nil { + + return "", err + } + stringBuilder.WriteString(when) stringBuilder.WriteString("\n then\n") - thens := parseThen(rule.Then) + thens, err := parseThen(rule.Then) + if err != nil { + return "", err + } for i := 0; i < len(thens); i++ { stringBuilder.WriteString(" ") stringBuilder.WriteString(thens[i]) @@ -207,10 +211,10 @@ func parseRule(rule *GruleJSON) string { } stringBuilder.WriteString("}\n") - return stringBuilder.String() + return stringBuilder.String(), nil } -func parseThen(ts []interface{}) []string { +func parseThen(ts []interface{}) ([]string, error) { thens := make([]string, len(ts)) for thenItem := 0; thenItem < len(ts); thenItem++ { switch thenType := ts[thenItem].(type) { @@ -220,44 +224,48 @@ func parseThen(ts []interface{}) []string { thens[thenItem] += ";" } case map[string]interface{}: - thens[thenItem] = buildExpression(thenType, 0) + ";" + bldExpr, err := buildExpression(thenType, 0) + if err != nil { + return nil, err + } + thens[thenItem] = bldExpr + ";" default: - panic("invalid then type, must be a string or an array of action objects") + return nil, fmt.Errorf("invalid then type, must be a string or an array of action objects") } } - return thens + return thens, nil } -func parseWhen(w interface{}) string { +func parseWhen(w interface{}) (string, error) { switch whenType := w.(type) { case string: - return whenType + return whenType, nil case map[string]interface{}: return buildExpression(whenType, 0) default: - panic("invalid when type, must be a string or an array of condition objects") + return "", fmt.Errorf("invalid when type, must be a string or an array of condition objects") } } -func buildExpression(input map[string]interface{}, depth int) string { - exp, _ := buildExpressionEx(input, depth) +func buildExpression(input map[string]interface{}, depth int) (string, error) { + exp, _, err := buildExpressionEx(input, depth) - return exp + return exp, err } -func buildExpressionEx(input map[string]interface{}, depth int) (string, bool) { +func buildExpressionEx(input map[string]interface{}, depth int) (string, bool, error) { if depth > 1024 { - panic("JSON nesting exceeded 1024 levels, aborting") + return "", false, fmt.Errorf("JSON nesting exceeded 1024 levels, aborting") } if len(input) > 1 { - panic("expression objects can only contain a single operation type") + return "", false, fmt.Errorf("expression objects can only contain a single operation type") } for key, value := range input { switch key { @@ -268,220 +276,265 @@ func buildExpressionEx(input map[string]interface{}, depth int) (string, bool) { return buildCompoundOperator(value, depth, " || ") case "eq": + opers, err := joinOperator(value, " == ") - return joinOperator(value, " == "), false + return opers, false, err case "not": + opers, err := joinOperator(value, " != ") - return joinOperator(value, " != "), false + return opers, false, err case "gt": + opers, err := joinOperator(value, " > ") - return joinOperator(value, " > "), false + return opers, false, err case "gte": + opers, err := joinOperator(value, " >= ") - return joinOperator(value, " >= "), false + return opers, false, err case "lt": + opers, err := joinOperator(value, " < ") - return joinOperator(value, " < "), false + return opers, false, err case "lte": + opers, err := joinOperator(value, " <= ") - return joinOperator(value, " <= "), false + return opers, false, err case "bor": + opers, err := joinOperator(value, " | ") - return joinOperator(value, " | "), false + return opers, false, err case "band": + opers, err := joinOperator(value, " & ") - return joinOperator(value, " & "), false + return opers, false, err case "plus": + opers, err := joinOperator(value, " + ") - return joinOperator(value, " + "), false + return opers, false, err case "minus": + opers, err := joinOperator(value, " - ") - return joinOperator(value, " - "), false + return opers, false, err case "div": + opers, err := joinOperator(value, " / ") - return joinOperator(value, " / "), false + return opers, false, err case "mul": + opers, err := joinOperator(value, " * ") - return joinOperator(value, " * "), false + return opers, false, err case "mod": + opers, err := joinOperator(value, " % ") - return joinOperator(value, " % "), false + return opers, false, err case "set": + opers, err := joinSet(value, " = ") - return joinSet(value, " = "), true + return opers, true, err case "call": + joinStr, err := joinCall(value) - return joinCall(value), true + return joinStr, true, err case "obj": if s, ok := value.(string); ok { - return s, true + return s, true, nil } - panic("object must be a string") + return "", false, fmt.Errorf("object must be a string") case "const": switch valueType := value.(type) { case string: - return strconv.Quote(valueType), true + return strconv.Quote(valueType), true, nil case float64: - return strconv.FormatFloat(valueType, 'f', -1, 64), true + return strconv.FormatFloat(valueType, 'f', -1, 64), true, nil case bool: if valueType { - return "true", true + return "true", true, nil } - return "false", true + return "false", true, nil } - panic("constant must be a string or a numeric value") + return "", false, fmt.Errorf("constant must be a string or a numeric value") default: - panic("unknown operator type: " + key) + return "", false, fmt.Errorf("unknown operator type: " + key) } } - panic("boolean expression cannot be empty") + return "", false, fmt.Errorf("boolean expression cannot be empty") } -func buildCompoundOperator(o interface{}, depth int, operator string) (string, bool) { +func buildCompoundOperator(o interface{}, depth int, operator string) (string, bool, error) { if andarr, ok := o.([]interface{}); ok { var ands []string if len(andarr) < 2 { - panic("and operator must have at least 2 operands") + return "", false, fmt.Errorf("and operator must have at least 2 operands") } for i := 0; i < len(andarr); i++ { if subVal, ok := andarr[i].(map[string]interface{}); ok { - ands = append(ands, buildExpression(subVal, depth+1)) + bldexpr, err := buildExpression(subVal, depth+1) + if err != nil { + + return "", false, err + } + + ands = append(ands, bldexpr) } else { - panic("and operands must be an array of objects") + return "", false, fmt.Errorf("and operands must be an array of objects") } } if depth > 0 { - return "(" + strings.Join(ands, operator) + ")", false + return "(" + strings.Join(ands, operator) + ")", false, nil } - return strings.Join(ands, operator), false + return strings.Join(ands, operator), false, nil } - panic("compound operator must be an array") + return "", false, fmt.Errorf("compound operator must be an array") } -func joinCall(v interface{}) string { +func joinCall(v interface{}) (string, error) { if arr, ok := v.([]interface{}); ok { if len(arr) == 0 { - panic("call operator must have at least one operand") + return "", fmt.Errorf("call operator must have at least one operand") } var firstCallOperand string var ok bool if firstCallOperand, ok = arr[0].(string); !ok { - panic("first call operand must be a string") + return "", fmt.Errorf("first call operand must be a string") } if len(arr) > 1 { sars := make([]string, len(arr)-1) for i := 1; i < len(arr); i++ { - sars[i-1] = parseCallOperand(arr[i]) + operandStr, err := parseCallOperand(arr[i]) + if err != nil { + return "", err + } + sars[i-1] = operandStr } - return firstCallOperand + "(" + strings.Join(sars, ", ") + ")" + return firstCallOperand + "(" + strings.Join(sars, ", ") + ")", nil } - return firstCallOperand + "()" + return firstCallOperand + "()", nil } - panic("operator has an unexpected type") + return "", fmt.Errorf("operator has an unexpected type") } -func parseCallOperand(o interface{}) string { +func parseCallOperand(o interface{}) (string, error) { switch operandType := o.(type) { case string: if len(operandType) == 0 { - panic("operand cannnot be empty") + return "", fmt.Errorf("operand cannnot be empty") } - return operandType + return operandType, nil case float64: - return fmt.Sprint(operandType) + return fmt.Sprint(operandType), nil case bool: if operandType { - return "true" + return "true", nil } - return "false" + return "false", nil case map[string]interface{}: return buildExpression(operandType, 0) default: - panic("operand has an invalid type") + return "", fmt.Errorf("operand has an invalid type") } } -func joinOperator(v interface{}, operator string) string { +func joinOperator(v interface{}, operator string) (string, error) { if arr, ok := v.([]interface{}); ok { if len(arr) == 0 { - panic("operator cannot have 0 operands") + return "", fmt.Errorf("operator cannot have 0 operands") } ops := make([]string, len(arr)) for i := 0; i < len(arr); i++ { - ops[i] = parseOperand(arr[i], false) + ope, err := parseOperand(arr[i], false) + if err != nil { + + return "", err + } + ops[i] = ope } - return strings.Join(ops, operator) + return strings.Join(ops, operator), nil } - panic("operator has an unexpected type") + return "", fmt.Errorf("operator has an unexpected type") } -func joinSet(v interface{}, operator string) string { +func joinSet(v interface{}, operator string) (string, error) { if arr, ok := v.([]interface{}); ok { if len(arr) != 2 { - panic("set operand count must be 2") + return "", fmt.Errorf("set operand count must be 2") } + leftOpe, err := parseOperand(arr[0], true) + if err != nil { - return parseOperand(arr[0], true) + operator + parseOperand(arr[1], true) + return "", err + } + rightOpe, err := parseOperand(arr[1], true) + if err != nil { + + return "", err + } + + return leftOpe + operator + rightOpe, nil } - panic("operator has an unexpected type") + return "", fmt.Errorf("operator has an unexpected type") } -func parseOperand(o interface{}, noWrap bool) string { +func parseOperand(o interface{}, noWrap bool) (string, error) { switch operandType := o.(type) { case string: - return operandType + return operandType, nil case float64: - return fmt.Sprint(operandType) + return fmt.Sprint(operandType), nil case bool: if operandType { - return "true" + return "true", nil } - return "false" + return "false", nil case map[string]interface{}: - expr, expNoWrap := buildExpressionEx(operandType, 0) + expr, expNoWrap, err := buildExpressionEx(operandType, 0) + + if err != nil { + + return expr, err + } if expNoWrap || noWrap { - return expr + return expr, nil } - return "(" + expr + ")" + return "(" + expr + ")", nil default: - panic("operand has an invalid type") + return "", fmt.Errorf("operand has an invalid type") } } diff --git a/pkg/JsonResource_test.go b/pkg/JsonResource_test.go index aee95251..9b71bc3f 100644 --- a/pkg/JsonResource_test.go +++ b/pkg/JsonResource_test.go @@ -15,6 +15,7 @@ package pkg import ( + "github.com/stretchr/testify/assert" "testing" ) @@ -210,118 +211,54 @@ const expectedBigIntConversion = `rule SpeedUp "When testcar is speeding up we k func TestParseJSONRuleset(t *testing.T) { rs, err := ParseJSONRule([]byte(jsonData)) - if err != nil { - t.Fatal("Failed to parse flat rule: " + err.Error()) - } - t.Log("Flat rule output:") - t.Log(rs) - if rs != expectedRule { - t.Fatal("Parsed rule does not match expected result") - } + assert.NoError(t, err) + assert.Equal(t, expectedRule, rs) rs, err = ParseJSONRuleset([]byte(arrayJSONData)) - if err != nil { - t.Fatal("Failed to parse flat ruleset: " + err.Error()) - } - t.Log("Flat rule output:") - t.Log(rs) - if rs != expectedRule { - t.Fatal("Parsed rule does not match expected result") - } + assert.NoError(t, err) + assert.Equal(t, expectedRule, rs) rs, err = ParseJSONRule([]byte(jsonDataExpanded)) - if err != nil { - t.Fatal("Failed to parse expanded rule: " + err.Error()) - } - t.Log("Expanded rule output:") - t.Log(rs) - if rs != expectedRule { - t.Fatal("Parsed rule does not match expected result") - } + assert.NoError(t, err) + assert.Equal(t, expectedRule, rs) rs, err = ParseJSONRuleset([]byte(arrayJSONDataExpanded)) - if err != nil { - t.Fatal("Failed to parse expanded ruleset: " + err.Error()) - } - t.Log("Expanded rule output:") - t.Log(rs) - if rs != expectedRule { - t.Fatal("Parsed rule does not match expected result") - } + assert.NoError(t, err) + assert.Equal(t, expectedRule, rs) rs, err = ParseJSONRule([]byte(jsonDataVerbose)) - if err != nil { - t.Fatal("Failed to parse verbose rule: " + err.Error()) - } - t.Log("Verbose rule output:") - t.Log(rs) - if rs != expectedRule { - t.Fatal("Parsed rule does not match expected result") - } + assert.NoError(t, err) + assert.Equal(t, expectedRule, rs) rs, err = ParseJSONRuleset([]byte(arrayJSONDataVerbose)) - if err != nil { - t.Fatal("Failed to parse verbose ruleset: " + err.Error()) - } - t.Log("Verbose rule output:") - t.Log(rs) - if rs != expectedRule { - t.Fatal("Parsed rule does not match expected result") - } + assert.NoError(t, err) + assert.Equal(t, expectedRule, rs) } func TestNewJSONResourceFromResource(t *testing.T) { underlyingResource := NewBytesResource([]byte(jsonDataExpanded)) - resource := NewJSONResourceFromResource(underlyingResource) + resource, err := NewJSONResourceFromResource(underlyingResource) + assert.NoError(t, err) loaded, err := resource.Load() - if err != nil { - t.Fatal("Failed to load JSON rule: " + err.Error()) - } - t.Log(string(loaded)) - if string(loaded) != expectedRule { - t.Fatal("Loaded rule does not match expected result") - } + assert.NoError(t, err) + assert.Equal(t, expectedRule, string(loaded)) underlyingResource = NewBytesResource([]byte(arrayJSONDataExpanded)) - resource = NewJSONResourceFromResource(underlyingResource) + resource, err = NewJSONResourceFromResource(underlyingResource) + assert.NoError(t, err) loaded, err = resource.Load() - if err != nil { - t.Fatal("Failed to load JSON ruleset: " + err.Error()) - } - t.Log(string(loaded)) - if string(loaded) != expectedRule { - t.Fatal("Loaded rule does not match expected result") - } + assert.NoError(t, err) + assert.Equal(t, expectedRule, string(loaded)) } func TestJSONStringEscaping(t *testing.T) { rs, err := ParseJSONRule([]byte(jsonDataEscaped)) - if err != nil { - t.Fatal("Failed to parse flat rule: " + err.Error()) - } - t.Log(rs) - if rs != expectedEscaped { - t.Fatal("Rule output doe not match expected value") - } + assert.NoError(t, err) + assert.Equal(t, expectedEscaped, rs) rs, err = ParseJSONRuleset([]byte(arrayJSONDataEscaped)) - if err != nil { - t.Fatal("Failed to parse flat ruleset: " + err.Error()) - } - t.Log(rs) - if rs != expectedEscaped { - t.Fatal("Rule output doe not match expected value") - } + assert.NoError(t, err) + assert.Equal(t, expectedEscaped, rs) } func TestJSONBigIntConversion(t *testing.T) { rs, err := ParseJSONRule([]byte(jsonDataBigIntConversion)) - if err != nil { - t.Fatal("Failed to parse flat rule: " + err.Error()) - } - t.Log(rs) - if rs != expectedBigIntConversion { - t.Fatal("Rule output doe not match expected value") - } + assert.NoError(t, err) + assert.Equal(t, expectedBigIntConversion, rs) rs, err = ParseJSONRuleset([]byte(arrayJSONDataBigIntConversion)) - if err != nil { - t.Fatal("Failed to parse flat ruleset: " + err.Error()) - } - t.Log(rs) - if rs != expectedBigIntConversion { - t.Fatal("Rule output doe not match expected value") - } + assert.NoError(t, err) + assert.Equal(t, expectedBigIntConversion, rs) } diff --git a/pkg/embeddedResource.go b/pkg/embeddedResource.go index c77a8781..1a868969 100644 --- a/pkg/embeddedResource.go +++ b/pkg/embeddedResource.go @@ -52,10 +52,7 @@ func (res *EmbeddedResource) Load() ([]byte, error) { return res.Bytes, nil } - var err error - res.Bytes, err = res.Source.ReadFile(res.Path) - - return res.Bytes, err + return res.Source.ReadFile(res.Path) } func (res *EmbeddedResource) String() string { diff --git a/pkg/jsontool/JsonDom.go b/pkg/jsontool/JsonDom.go index 1aecf7dd..4513748e 100644 --- a/pkg/jsontool/JsonDom.go +++ b/pkg/jsontool/JsonDom.go @@ -83,140 +83,149 @@ func (n *JSONNode) IsInt() bool { return true } -// Len return length of element in this array. Will panic if this node is not an array +// Len return length of element in this array. func (n *JSONNode) Len() int { if !n.IsArray() { - panic("Not array") + return -1 } arr := n.interf.([]interface{}) return len(arr) } -// GetNodeAt will get the child not on specific index. Will panic if this not is not an array -func (n *JSONNode) GetNodeAt(index int) *JSONNode { +// GetNodeAt will get the child not on specific index. +func (n *JSONNode) GetNodeAt(index int) (*JSONNode, error) { if !n.IsArray() { - panic("Not array") + return nil, fmt.Errorf("not array") } arr := n.interf.([]interface{}) - return &JSONNode{interf: arr[index]} + return &JSONNode{interf: arr[index]}, nil } -// HaveKey will check if the map contains specified key. Will panic if this node is not a map -func (n *JSONNode) HaveKey(key string) bool { +// HaveKey will check if the map contains specified key. +func (n *JSONNode) HaveKey(key string) (bool, error) { if !n.IsMap() { - panic("Not map") + return false, fmt.Errorf("not map") } amap := n.interf.(map[string]interface{}) if _, ok := amap[key]; ok { - return ok + return ok, nil } - return false + return false, nil } -// Get will fetch the child not designated with specified key. Will panic if this node is not a map -func (n *JSONNode) Get(key string) *JSONNode { +// Get will fetch the child not designated with specified key. +func (n *JSONNode) Get(key string) (*JSONNode, error) { if !n.IsMap() { - panic("Not map") + return nil, fmt.Errorf("not map") } amap := n.interf.(map[string]interface{}) - return &JSONNode{interf: amap[key]} + return &JSONNode{interf: amap[key]}, nil } -// Set will set the value of a map designated with specified key. Will panic if this node is not a map -func (n *JSONNode) Set(key string, node *JSONNode) { +// Set will set the value of a map designated with specified key. +func (n *JSONNode) Set(key string, node *JSONNode) error { if !n.IsMap() { - panic("Not map") + return fmt.Errorf("not map") } amap := n.interf.(map[string]interface{}) amap[key] = node.interf + + return nil } -// GetString will get the string value of this node. Will panic if this node is not a string -func (n *JSONNode) GetString() string { +// GetString will get the string value of this node. +func (n *JSONNode) GetString() (string, error) { if !n.IsString() { - panic("Not string") + return "", fmt.Errorf("not string") } - return n.interf.(string) + return n.interf.(string), nil } -// SetString will set this node value with a string value. Will panic if this node is not a string -func (n *JSONNode) SetString(val string) { +// SetString will set this node value with a string value. +func (n *JSONNode) SetString(val string) error { if !n.IsString() { - panic("Not string") + return fmt.Errorf("not string") } n.interf = val + return nil } -// GetBool will get the bool value of this node. Will panic if this node is not a boolean -func (n *JSONNode) GetBool() bool { +// GetBool will get the bool value of this node. +func (n *JSONNode) GetBool() (bool, error) { if !n.IsBool() { - panic("Not boolean") + return false, fmt.Errorf("not boolean") } - return n.interf.(bool) + return n.interf.(bool), nil } -// SetBool will set this node value with boolean value, will panic if this node is not a bool -func (n *JSONNode) SetBool(val bool) { +// SetBool will set this node value with boolean value, +func (n *JSONNode) SetBool(val bool) error { if !n.IsBool() { - panic("Not boolean") + return fmt.Errorf("not boolean") } n.interf = val + + return nil } -// GetFloat will get the float value of this node. Will panic if this node is not a float. -func (n *JSONNode) GetFloat() float64 { +// GetFloat will get the float value of this node. +func (n *JSONNode) GetFloat() (float64, error) { if !n.IsFloat() { - panic("Not float") + return 0, fmt.Errorf("not float") } - return n.interf.(float64) + return n.interf.(float64), nil } -// SetFloat will set this node value with float value. Will panic if this node is not a float -func (n *JSONNode) SetFloat(val float64) { +// SetFloat will set this node value with float value. +func (n *JSONNode) SetFloat(val float64) error { if !n.IsFloat() { - panic("Not float") + return fmt.Errorf("not float") } n.interf = val + + return nil } -// GetInt will get the int value of this node. Will panic if this node is not an int -func (n *JSONNode) GetInt() int { +// GetInt will get the int value of this node. +func (n *JSONNode) GetInt() (int, error) { if !n.IsInt() { - panic("Not int") + return 0, fmt.Errorf("not int") } fl := n.interf.(float64) - return int(fl) + return int(fl), nil } -// SetInt will set this node value with int value. Will panic if this node is not an int -func (n *JSONNode) SetInt(val int) { +// SetInt will set this node value with int value. +func (n *JSONNode) SetInt(val int) error { if !n.IsInt() { - panic("Not int") + return fmt.Errorf("not int") } n.interf = float64(val) + + return nil } // JSONData represent a whole Json construct. @@ -225,157 +234,220 @@ type JSONData struct { } // GetRootNode will return the root node of this JSONData -func (jo *JSONData) GetRootNode() *JSONNode { +func (jo *JSONData) GetRootNode() (*JSONNode, error) { if jo.jsonRoot == nil { - panic(fmt.Sprintf("root node is nil")) + return nil, fmt.Errorf("root node is nil") } - return &JSONNode{interf: jo.jsonRoot} + return &JSONNode{interf: jo.jsonRoot}, nil } // IsValidPath will check if the provided path is valid -func (jo *JSONData) IsValidPath(path string) bool { +func (jo *JSONData) IsValidPath(path string) (bool, error) { if len(path) == 0 { - return true + return true, nil } pathArr := strings.Split(path, ".") - node := jo.GetRootNode() + node, err := jo.GetRootNode() + if err != nil { + + return false, err + } - return jo.validPathCheck(pathArr, node) + boolres, err := jo.validPathCheck(pathArr, node) + if err != nil { + + return false, err + } + + return boolres, nil } // validPathCheck is recursion function to traverse the json tree for checking valid path -func (jo *JSONData) validPathCheck(pathArr []string, node *JSONNode) bool { +func (jo *JSONData) validPathCheck(pathArr []string, node *JSONNode) (bool, error) { if len(pathArr) == 0 && (node.IsString() || node.IsInt() || node.IsFloat() || node.IsBool()) { - return true + return true, nil } path := pathArr[0] if len(path) == 0 { - return false + return false, nil } if path[:1] == "[" && path[len(path)-1:] == "]" { if node.IsArray() { pn := path[1 : len(path)-1] if len(pn) == 0 { - return false + return false, nil } theInt, err := strconv.Atoi(pn) if err != nil { - return false + + return false, nil } if theInt < 0 || theInt >= node.Len() { - return false + return false, nil + } + nNode, err := node.GetNodeAt(theInt) + if err != nil { + return false, err } - nNode := node.GetNodeAt(theInt) nPathArr := pathArr[1:] return jo.validPathCheck(nPathArr, nNode) } - return false + return false, nil } if node.IsMap() { if strings.Contains(path, "[") { k := path[:strings.Index(path, "[")] - if !node.HaveKey(k) { + haveKey, err := node.HaveKey(k) + if err != nil { + + return false, err + } + if !haveKey { - return false + return false, nil + } + nNode, err := node.Get(k) + if err != nil { + + return false, err } - nNode := node.Get(k) nPathArr := []string{path[strings.Index(path, "["):]} nPathArr = append(nPathArr, pathArr[1:]...) - return jo.validPathCheck(nPathArr, nNode) + boolret, err := jo.validPathCheck(nPathArr, nNode) + if err != nil { + + return false, err + } + + return boolret, nil } - if node.HaveKey(path) { - nNode := node.Get(path) + hKey, err := node.HaveKey(path) + if err != nil { + + return false, err + } + if hKey { + nNode, err := node.Get(path) + if err != nil { + return false, err + } nPathArr := pathArr[1:] return jo.validPathCheck(nPathArr, nNode) } - return false + return false, nil } - return false + return false, nil } // Get will retrieve the json node indicated by a path -func (jo *JSONData) Get(path string) *JSONNode { +func (jo *JSONData) Get(path string) (*JSONNode, error) { if len(path) == 0 { return jo.GetRootNode() } pathArr := strings.Split(path, ".") - return jo.getByPath(pathArr, jo.GetRootNode()) + rNode, err := jo.GetRootNode() + if err != nil { + return nil, err + } + + return jo.getByPath(pathArr, rNode) } // getByPath is recursion function to traverse the json tree for retrieving node at specified path -func (jo *JSONData) getByPath(pathArr []string, node *JSONNode) *JSONNode { +func (jo *JSONData) getByPath(pathArr []string, node *JSONNode) (*JSONNode, error) { if len(pathArr) == 0 && (node.IsString() || node.IsInt() || node.IsFloat() || node.IsBool()) { - return node + return node, nil } path := pathArr[0] if len(path) == 0 { - panic("Not a valid path") + return nil, fmt.Errorf("%s not a valid path", strings.Join(pathArr, ".")) } if path[:1] == "[" && path[len(path)-1:] == "]" { if node.IsArray() { pn := path[1 : len(path)-1] if len(pn) == 0 { - panic("Not a valid path - array do not contain offset number") + return nil, fmt.Errorf("not a valid path - array do not contain offset number") } theInt, err := strconv.Atoi(pn) if err != nil { - panic("Not a valid path - array offset not number") + return nil, fmt.Errorf("not a valid path - array offset not number") } if theInt < 0 || theInt >= node.Len() { - panic("Not a valid path - array offset < 0 or >= length") + return nil, fmt.Errorf("not a valid path - array offset < 0 or >= length") + } + nNode, err := node.GetNodeAt(theInt) + if err != nil { + + return nil, err } - nNode := node.GetNodeAt(theInt) nPathArr := pathArr[1:] return jo.getByPath(nPathArr, nNode) } - panic("Not a valid path - not an array") + return nil, fmt.Errorf("not a valid path - not an array") } if node.IsMap() { if strings.Contains(path, "[") { k := path[:strings.Index(path, "[")] - if !node.HaveKey(k) { + haveKe, err := node.HaveKey(k) + if err != nil { + + return nil, err + } + if !haveKe { - panic("Not a valid path - key not exist") + return nil, fmt.Errorf("not a valid path - key not exist") + } + nNode, err := node.Get(k) + if err != nil { + + return nil, err } - nNode := node.Get(k) nPathArr := []string{path[strings.Index(path, "["):]} nPathArr = append(nPathArr, pathArr[1:]...) return jo.getByPath(nPathArr, nNode) } - if node.HaveKey(path) { - nNode := node.Get(path) + haveKy, err := node.HaveKey(path) + if err != nil { + return nil, err + } + if haveKy { + nNode, err := node.Get(path) + if err != nil { + + return nil, err + } nPathArr := pathArr[1:] return jo.getByPath(nPathArr, nNode) } - panic("Not a valid path - key not exist") + return nil, fmt.Errorf("not a valid path - key not exist") } - panic("Not a valid path") + return nil, fmt.Errorf("not a valid path") } // GetString will get the string value from a json indicated by specified path. Error returned if path is not valid. @@ -389,9 +461,11 @@ func (jo *JSONData) GetString(path string) (string, error) { return "", fmt.Errorf("%s is not a string", path) } - node := jo.Get(path) - - return node.GetString(), nil + node, err := jo.Get(path) + if err != nil { + return "", err + } + return node.GetString() } // SetString will set the node at specified path with provided string value @@ -412,9 +486,11 @@ func (jo *JSONData) GetBool(path string) (bool, error) { return false, fmt.Errorf("%s is not a boolean", path) } - node := jo.Get(path) - - return node.GetBool(), nil + node, err := jo.Get(path) + if err != nil { + return false, err + } + return node.GetBool() } // SetBool will set the node at specified path with provided bool value @@ -435,9 +511,11 @@ func (jo *JSONData) GetFloat(path string) (float64, error) { return 0, fmt.Errorf("%s is not a float", path) } - node := jo.Get(path) - - return node.GetFloat(), nil + node, err := jo.Get(path) + if err != nil { + return 0, err + } + return node.GetFloat() } // SetFloat will set the node at specified path with provided float value @@ -458,9 +536,13 @@ func (jo *JSONData) GetInt(path string) (int, error) { return 0, fmt.Errorf("%s is not an int", path) } - node := jo.Get(path) + node, err := jo.Get(path) + if err != nil { + + return 0, err + } - return node.GetInt(), nil + return node.GetInt() } // SetInt will set the node at specified path with provided int value @@ -472,60 +554,126 @@ func (jo *JSONData) SetInt(path string, value int) error { // IsArray will check if the node indicated by specified path is an Array node func (jo *JSONData) IsArray(path string) (bool, error) { - if !jo.IsValidPath(path) { + vp, err := jo.IsValidPath(path) + if err != nil { + + return false, err + } + if !vp { return false, fmt.Errorf("%s is not a valid path", path) } - return jo.Get(path).IsArray(), nil + jsonN, err := jo.Get(path) + if err != nil { + + return false, err + } + boolRet := jsonN.IsArray() + + return boolRet, nil } // IsMap will check if the node indicated by specified path is a map node func (jo *JSONData) IsMap(path string) (bool, error) { - if !jo.IsValidPath(path) { + isValPath, err := jo.IsValidPath(path) + if err != nil { + + return false, err + } + if !isValPath { return false, fmt.Errorf("%s is not a valid path", path) } + node, err := jo.Get(path) + if err != nil { + + return false, err + } - return jo.Get(path).IsMap(), nil + return node.IsMap(), nil } // IsString will check if the node indicated by specified path is a string node func (jo *JSONData) IsString(path string) (bool, error) { - if !jo.IsValidPath(path) { + isValPath, err := jo.IsValidPath(path) + if err != nil { + + return false, err + } + if !isValPath { return false, fmt.Errorf("%s is not a valid path", path) } - return jo.Get(path).IsString(), nil + node, err := jo.Get(path) + if err != nil { + + return false, err + } + + return node.IsString(), nil } // IsBool will check if the node indicated by specified path is a bool node func (jo *JSONData) IsBool(path string) (bool, error) { - if !jo.IsValidPath(path) { + isValPath, err := jo.IsValidPath(path) + if err != nil { + + return false, err + } + if !isValPath { return false, fmt.Errorf("%s is not a valid path", path) } - return jo.Get(path).IsBool(), nil + node, err := jo.Get(path) + if err != nil { + + return false, err + } + + return node.IsBool(), nil } // IsFloat will check if the node indicated by specified path is a float node func (jo *JSONData) IsFloat(path string) (bool, error) { - if !jo.IsValidPath(path) { + isValPath, err := jo.IsValidPath(path) + if err != nil { + + return false, err + } + if !isValPath { return false, fmt.Errorf("%s is not a valid path", path) } - return jo.Get(path).IsFloat(), nil + node, err := jo.Get(path) + if err != nil { + + return false, err + } + + return node.IsFloat(), nil } // IsInt will check if the node indicated by specified path is an int node func (jo *JSONData) IsInt(path string) (bool, error) { - if !jo.IsValidPath(path) { + isValPath, err := jo.IsValidPath(path) + if err != nil { + + return false, err + } + if !isValPath { return false, fmt.Errorf("%s is not a valid path", path) } - return jo.Get(path).IsInt(), nil + node, err := jo.Get(path) + if err != nil { + + return false, err + } + + return node.IsInt(), nil } diff --git a/pkg/jsontool/JsonDom_test.go b/pkg/jsontool/JsonDom_test.go index 0b3a1837..d501946c 100644 --- a/pkg/jsontool/JsonDom_test.go +++ b/pkg/jsontool/JsonDom_test.go @@ -110,7 +110,10 @@ func TestNewJsonObject(t *testing.T) { } else { typ := reflect.TypeOf(jobj.jsonRoot) t.Logf("Data : %d - %s. Type is : %s ", i, v, typ.String()) - node := jobj.GetRootNode() + node, err := jobj.GetRootNode() + if err != nil { + t.Fail() + } switch JSONType[i] { case "obj": if !node.IsMap() { @@ -161,7 +164,25 @@ func TestJsonNodeOperations(t *testing.T) { t.FailNow() } - if jdata.GetRootNode().Get("fullname").GetString() != "Bruce Wayne" { + rNode, err := jdata.GetRootNode() + if err != nil { + t.Logf("Got error %s", err.Error()) + t.FailNow() + } + + jnode, err := rNode.Get("fullname") + if err != nil { + t.Logf("Got error %s", err.Error()) + t.FailNow() + } + + sn, err := jnode.GetString() + if err != nil { + t.Logf("Got error %s", err.Error()) + t.FailNow() + } + + if sn != "Bruce Wayne" { t.Logf("fail validate full name") t.Fail() } @@ -174,7 +195,20 @@ func TestJsonNodeOperations(t *testing.T) { t.Fail() } - if jdata.GetRootNode().Get("age").GetInt() != 35 { + rnode, err := jdata.GetRootNode() + if err != nil { + t.Fail() + } + ageNode, err := rnode.Get("age") + if err != nil { + t.Fail() + } + theInt, err := ageNode.GetInt() + if err != nil { + t.Fail() + } + + if theInt != 35 { t.Logf("fail validate age") t.Fail() } @@ -187,7 +221,31 @@ func TestJsonNodeOperations(t *testing.T) { t.Fail() } - if jdata.GetRootNode().Get("address").Get("street1").GetString() != "Super Mansion" { + rnode, err = jdata.GetRootNode() + if err != nil { + t.Logf("Got error %s", err.Error()) + t.FailNow() + } + + addrNode, err := rnode.Get("address") + if err != nil { + t.Logf("Got error %s", err.Error()) + t.FailNow() + } + + strtNode, err := addrNode.Get("street1") + if err != nil { + t.Logf("Got error %s", err.Error()) + t.FailNow() + } + + strValue, err := strtNode.GetString() + if err != nil { + t.Logf("Got error %s", err.Error()) + t.FailNow() + } + + if strValue != "Super Mansion" { t.Logf("fail validate address.street1") t.Fail() } @@ -210,7 +268,16 @@ func TestJsonNodeOperations(t *testing.T) { t.Fail() } - if !jdata.GetRootNode().Get("friends").IsArray() { + rNode, err = jdata.GetRootNode() + if err != nil { + t.FailNow() + } + nNode, err := rNode.Get("friends") + if err != nil { + t.FailNow() + } + + if !nNode.IsArray() { t.Logf("fail validate friends as array") t.Fail() } @@ -223,7 +290,23 @@ func TestJsonNodeOperations(t *testing.T) { t.Fail() } - if !jdata.GetRootNode().Get("friends").GetNodeAt(1).Get("fullname").IsString() { + rNode, err = jdata.GetRootNode() + if err != nil { + t.FailNow() + } + nNode, err = rNode.Get("friends") + if err != nil { + t.FailNow() + } + nNodeAt, err := nNode.GetNodeAt(1) + if err != nil { + t.FailNow() + } + nNode2, err := nNodeAt.Get("fullname") + if err != nil { + t.FailNow() + } + if !nNode2.IsString() { t.Logf("fail validate friends[1].fullname type") t.Fail() } @@ -236,11 +319,31 @@ func TestJsonNodeOperations(t *testing.T) { t.Fail() } - if jdata.GetRootNode().Get("friends").GetNodeAt(1).Get("fullname").GetString() != "Lara Croft" { + rNode, err = jdata.GetRootNode() + if err != nil { + t.FailNow() + } + nNode, err = rNode.Get("friends") + if err != nil { + t.FailNow() + } + nNodeAt, err = nNode.GetNodeAt(1) + if err != nil { + t.FailNow() + } + nNode2, err = nNodeAt.Get("fullname") + if err != nil { + t.FailNow() + } + str, err := nNode2.GetString() + if err != nil { + t.FailNow() + } + if str != "Lara Croft" { t.Logf("fail validate friends[1].fullname value") t.Fail() } - str, err := jdata.GetString("friends[1].fullname") + str, err = jdata.GetString("friends[1].fullname") if err != nil { t.Errorf(err.Error()) } @@ -250,66 +353,43 @@ func TestJsonNodeOperations(t *testing.T) { } } +type PathTest struct { + Path string + Valid bool +} + func TestJsonData_IsValidPath(t *testing.T) { + + pTests := make([]PathTest, 0) + + pTests = append(pTests, PathTest{Path: "fullname", Valid: true}) + pTests = append(pTests, PathTest{Path: "fullname.", Valid: false}) + pTests = append(pTests, PathTest{Path: "fullname.abc", Valid: false}) + pTests = append(pTests, PathTest{Path: "abc", Valid: false}) + pTests = append(pTests, PathTest{Path: "", Valid: true}) + pTests = append(pTests, PathTest{Path: "address.street1", Valid: true}) + pTests = append(pTests, PathTest{Path: "address.street5", Valid: false}) + pTests = append(pTests, PathTest{Path: "friends", Valid: true}) + pTests = append(pTests, PathTest{Path: "friends[1]", Valid: true}) + pTests = append(pTests, PathTest{Path: "friends[]", Valid: false}) + pTests = append(pTests, PathTest{Path: "friends[10]", Valid: false}) + pTests = append(pTests, PathTest{Path: "friends[1].address.street1", Valid: true}) + pTests = append(pTests, PathTest{Path: "friends[1].abc.street1", Valid: false}) + jdata, err := NewJSONData([]byte(bigJSON)) if err != nil { t.Logf("Got error %s", err.Error()) t.FailNow() } - if !jdata.IsValidPath("fullname") { - t.Logf("fullname is a valid path") - t.Fail() - } - if jdata.IsValidPath("fullname.") { - t.Logf("fullname. is not a valid path") - t.Fail() - } - if jdata.IsValidPath("fullname.abc") { - t.Logf("fullname.abc is not a valid path") - t.Fail() - } - if jdata.IsValidPath("fullname[]") { - t.Logf("fullname[] is not a valid path") - t.Fail() - } - if jdata.IsValidPath("abc") { - t.Logf("abs is not a valid path") - t.Fail() - } - if !jdata.IsValidPath("") { - t.Logf("empty string is a valid path") - t.Fail() - } - if !jdata.IsValidPath("address.street1") { - t.Logf("\"address.street1\" is a valid path") - t.Fail() - } - if jdata.IsValidPath("address.street5") { - t.Logf("\"address.street5\" is NOT a valid path") - t.Fail() - } - if !jdata.IsValidPath("friends") { - t.Logf("\"friends\" is a valid path") - t.Fail() - } - if !jdata.IsValidPath("friends[1]") { - t.Logf("\"friends[1]\" is a valid path") - t.Fail() - } - if jdata.IsValidPath("friends[]") { - t.Logf("\"friends[]\" is NOT a valid path") - t.Fail() - } - if jdata.IsValidPath("friends[10]") { - t.Logf("\"friends[10]\" is NOT a valid path") - t.Fail() - } - if !jdata.IsValidPath("friends[1].address.street1") { - t.Logf("\"friends[1].address.street1\" is a valid path") - t.Fail() - } - if jdata.IsValidPath("friends[1].abc.street1") { - t.Logf("\"friends[1].abc.street1\" is a valid path") - t.Fail() + + for _, ptest := range pTests { + if val, err := jdata.IsValidPath(ptest.Path); err != nil { + t.Logf("got error %s", err.Error()) + t.FailNow() + } else if val != ptest.Valid { + t.Logf("'%s' valid path expect '%v' but '%v'", ptest.Path, ptest.Valid, val) + t.FailNow() + } } + } diff --git a/pkg/resource.go b/pkg/resource.go index 3a4013d4..d246941c 100644 --- a/pkg/resource.go +++ b/pkg/resource.go @@ -382,13 +382,13 @@ func (bundle *GITResourceBundle) loadPath(url, path string, fileSyst billy.Files // MustLoad is the same as Load, the difference is it will panic if an error is raised during fetching resources. func (bundle *GITResourceBundle) MustLoad() []Resource { - resources, err := bundle.Load() + res, err := bundle.Load() if err != nil { panic(err) } - return resources + return res } // GITResource resource implementation that loaded from GIT