diff --git a/common/hyperscaler/rules/grammar/listener.go b/common/hyperscaler/rules/grammar/listener.go index f2f7fe499e..be321955c7 100644 --- a/common/hyperscaler/rules/grammar/listener.go +++ b/common/hyperscaler/rules/grammar/listener.go @@ -12,21 +12,31 @@ type RuleListener struct { } func (r RuleListener) EnterEntry(c *parser.EntryContext) { - r.processed.Plan = c.PLAN().GetText() + if c.PLAN() != nil { + r.processed.Plan = c.PLAN().GetText() + } } func (r *RuleListener) EnterPrVal(c *parser.PrValContext) { - r.processed.PlatformRegion = c.Val().GetText() + if c.Val() != nil { + r.processed.PlatformRegion = c.Val().GetText() + } } func (r *RuleListener) EnterHrVal(c *parser.HrValContext) { - r.processed.HyperscalerRegion = c.Val().GetText() + if c.Val() != nil { + r.processed.HyperscalerRegion = c.Val().GetText() + } } func (s *RuleListener) EnterS(c *parser.SContext) { - s.processed.Shared = true + if c.S() != nil { + s.processed.Shared = true + } } func (s *RuleListener) EnterEu(c *parser.EuContext) { - s.processed.EuAccess = true + if c.EU() != nil { + s.processed.EuAccess = true + } } diff --git a/common/hyperscaler/rules/grammar/parser.go b/common/hyperscaler/rules/grammar/parser.go index 9a2f5aa3b3..6ff1c02cf8 100644 --- a/common/hyperscaler/rules/grammar/parser.go +++ b/common/hyperscaler/rules/grammar/parser.go @@ -7,24 +7,54 @@ import ( ) type GrammarParser struct{ - } -func (g* GrammarParser) Parse(ruleEntry string) *rules.Rule { - // Setup the input +func (g* GrammarParser) Parse(ruleEntry string) (*rules.Rule, error) { is := antlr.NewInputStream(ruleEntry) - // Create the Lexer + erorrsListener := &ErrorListener{} + lexer := parser.NewRuleLexer(is) + lexer.RemoveErrorListeners() + lexer.AddErrorListener(erorrsListener) + stream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel) - // Create the Parser p := parser.NewRuleParserParser(stream) + + p.RemoveErrorListeners() + p.AddErrorListener(erorrsListener) - // Finally parse the expression listener := &RuleListener{processed: &rules.Rule{}} antlr.ParseTreeWalkerDefault.Walk(listener, p.RuleEntry()) - return listener.processed + + if len(erorrsListener.Errors) > 0 { + return nil, erorrsListener.Errors[0] + } + + return listener.processed, nil +} + +type SyntaxError struct { + line, column int + msg string +} + +func (c *SyntaxError) Error() string { + return c.msg +} + +type ErrorListener struct { + *antlr.DefaultErrorListener + Errors []error +} + +func (c *ErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) { + c.Errors = append(c.Errors, &SyntaxError{ + line: line, + column: column, + msg: msg, + }) } diff --git a/common/hyperscaler/rules/grammar/parser_test.go b/common/hyperscaler/rules/grammar/parser_test.go index 0a4209b559..25dba32d58 100644 --- a/common/hyperscaler/rules/grammar/parser_test.go +++ b/common/hyperscaler/rules/grammar/parser_test.go @@ -5,7 +5,6 @@ import "testing" import "github.com/kyma-project/kyma-environment-broker/common/hyperscaler/rules" func TestParser(t *testing.T) { - - rules.ParserTest(t, &GrammarParser{}) - + rules.ParserHappyPathTest(t, &GrammarParser{}) + rules.ParserValidationTest(t, &GrammarParser{}) } \ No newline at end of file diff --git a/common/hyperscaler/rules/parser.go b/common/hyperscaler/rules/parser.go index 8780a9beb1..c4a44fbee8 100644 --- a/common/hyperscaler/rules/parser.go +++ b/common/hyperscaler/rules/parser.go @@ -1,5 +1,5 @@ package rules type Parser interface { - Parse(ruleEntry string) *Rule + Parse(ruleEntry string) (*Rule, error) } \ No newline at end of file diff --git a/common/hyperscaler/rules/parser_test_utils.go b/common/hyperscaler/rules/parser_test_utils.go index 18298a5766..414fb55850 100644 --- a/common/hyperscaler/rules/parser_test_utils.go +++ b/common/hyperscaler/rules/parser_test_utils.go @@ -6,10 +6,11 @@ import ( "github.com/stretchr/testify/require" ) -func ParserTest(t *testing.T, parser Parser) { +func ParserHappyPathTest(t *testing.T, parser Parser) { t.Run("with plan", func(t *testing.T) { - rule := parser.Parse("azure") + rule, err := parser.Parse("azure") + require.NoError(t, err) require.NotNil(t, rule) require.Equal(t, "azure", rule.Plan) @@ -20,8 +21,9 @@ func ParserTest(t *testing.T, parser Parser) { require.Equal(t, false, rule.Shared) }) - t.Run("with plan and platform region", func(t *testing.T) { - rule := parser.Parse("azure(PR=westeurope)") + t.Run("with plan and single input attribute", func(t *testing.T) { + rule, err := parser.Parse("azure(PR=westeurope)") + require.NoError(t, err) require.NotNil(t, rule) require.Equal(t, "azure", rule.Plan) @@ -30,10 +32,9 @@ func ParserTest(t *testing.T, parser Parser) { require.Equal(t, false, rule.EuAccess) require.Equal(t, false, rule.Shared) - }) - t.Run("with plan and hyperscaler region", func(t *testing.T) { - rule := parser.Parse("azure(HR=westeurope)") + rule, err = parser.Parse("azure(HR=westeurope)") + require.NoError(t, err) require.NotNil(t, rule) require.Equal(t, "azure", rule.Plan) @@ -44,8 +45,20 @@ func ParserTest(t *testing.T, parser Parser) { require.Equal(t, false, rule.Shared) }) - t.Run("with plan, platform and hyperscaler region", func(t *testing.T) { - rule := parser.Parse("azure(PR=easteurope, HR=westeurope)") + t.Run("with plan all output attributes - different positions", func(t *testing.T) { + rule, err := parser.Parse("azure(PR=easteurope,HR=westeurope)") + require.NoError(t, err) + + require.NotNil(t, rule) + require.Equal(t, "azure", rule.Plan) + require.Equal(t, "westeurope", rule.HyperscalerRegion) + require.Equal(t, "easteurope", rule.PlatformRegion) + + require.False(t, rule.EuAccess) + require.False(t, rule.Shared) + + rule, err = parser.Parse("azure(HR=westeurope,PR=easteurope)") + require.NoError(t, err) require.NotNil(t, rule) require.Equal(t, "azure", rule.Plan) @@ -56,8 +69,9 @@ func ParserTest(t *testing.T, parser Parser) { require.False(t, rule.Shared) }) - t.Run("with plan and shared", func(t *testing.T) { - rule := parser.Parse("azure->S") + t.Run("with plan and single output attribute", func(t *testing.T) { + rule, err := parser.Parse("azure->S") + require.NoError(t, err) require.NotNil(t, rule) require.Equal(t, "azure", rule.Plan) @@ -66,11 +80,23 @@ func ParserTest(t *testing.T, parser Parser) { require.False(t, rule.EuAccess) require.True(t, rule.Shared) + + rule, err = parser.Parse("azure->EU") + require.NoError(t, err) + + require.NotNil(t, rule) + require.Equal(t, "azure", rule.Plan) + require.Empty(t, rule.HyperscalerRegion) + require.Empty(t, rule.PlatformRegion) + + require.True(t, rule.EuAccess) + require.False(t, rule.Shared) }) - t.Run("with plan, shared and euAccess", func(t *testing.T) { - rule := parser.Parse("azure->S,EU") + t.Run("with plan and all output attributes - different positions", func(t *testing.T) { + rule, err := parser.Parse("azure->S,EU") + require.NoError(t, err) require.NotNil(t, rule) require.Equal(t, "azure", rule.Plan) @@ -79,6 +105,78 @@ func ParserTest(t *testing.T, parser Parser) { require.True(t, rule.EuAccess) require.True(t, rule.Shared) + + rule, err = parser.Parse("azure->EU,S") + require.NoError(t, err) + + require.NotNil(t, rule) + require.Equal(t, "azure", rule.Plan) + require.Empty(t, rule.HyperscalerRegion) + require.Empty(t, rule.PlatformRegion) + + require.True(t, rule.EuAccess) + require.True(t, rule.Shared) + }) + + t.Run("with plan and single output/input attributes", func(t *testing.T) { + rule, err := parser.Parse("azure(PR=westeurope)->EU") + require.NoError(t, err) + + require.NotNil(t, rule) + require.Equal(t, "azure", rule.Plan) + require.Empty(t, rule.HyperscalerRegion) + require.Equal(t, "westeurope", rule.PlatformRegion) + + require.True(t, rule.EuAccess) + require.False(t, rule.Shared) + }) + + t.Run("with plan and all input/output attributes", func(t *testing.T) { + rule, err := parser.Parse("azure(PR=westeurope, HR=easteurope)->EU,S") + require.NoError(t, err) + + require.NotNil(t, rule) + require.Equal(t, "azure", rule.Plan) + require.Equal(t, "easteurope", rule.HyperscalerRegion) + require.Equal(t, "westeurope", rule.PlatformRegion) + + require.True(t, rule.EuAccess) + require.True(t, rule.Shared) + }) +} + + +func ParserValidationTest(t *testing.T, parser Parser) { + + t.Run("with paranthesis only", func(t *testing.T) { + rule, err := parser.Parse("()") + require.Nil(t, rule) + require.Error(t, err) + }) + + t.Run("with arrow only", func(t *testing.T) { + rule, err := parser.Parse("->") + require.Nil(t, rule) + require.Error(t, err) + }) + + t.Run("with incorrect attributes list", func(t *testing.T) { + rule, err := parser.Parse("test(,)->,") + require.Nil(t, rule) + require.Error(t, err) + + rule, err = parser.Parse("test(PR=west,HR=east)->,") + require.Nil(t, rule) + require.Error(t, err) }) +// t.Run("with duplicated input attribute", func(t *testing.T) { +// rule, err := parser.Parse("azure(PR=test,PR=test2)") +// require.Nil(t, rule) +// require.Error(t, err) + +// rule, err = parser.Parse("test(PR=west,HR=east)->EU,EU") +// require.Nil(t, rule) +// require.Error(t, err) +// }) } \ No newline at end of file diff --git a/common/hyperscaler/rules/simple_parser.go b/common/hyperscaler/rules/simple_parser.go index 170278019c..427b4d62f6 100644 --- a/common/hyperscaler/rules/simple_parser.go +++ b/common/hyperscaler/rules/simple_parser.go @@ -1,6 +1,7 @@ package rules import ( + "fmt" "strings" ) @@ -8,7 +9,7 @@ type SimpleParser struct{ } -func (g* SimpleParser) Parse(ruleEntry string) *Rule { +func (g* SimpleParser) Parse(ruleEntry string) (*Rule, error) { outputRule := &Rule{} outputInputPart := strings.Split(ruleEntry, "->") @@ -18,6 +19,9 @@ func (g* SimpleParser) Parse(ruleEntry string) *Rule { planAndInputAttr := strings.Split(inputPart, "(") outputRule.Plan = planAndInputAttr[0] + if outputRule.Plan == "" { + return nil, fmt.Errorf("plan is empty") + } if len(planAndInputAttr) > 1 { inputPart := strings.TrimSuffix(planAndInputAttr[1], ")") @@ -25,6 +29,11 @@ func (g* SimpleParser) Parse(ruleEntry string) *Rule { inputAttrs := strings.Split(inputPart, ",") for _, inputAttr := range inputAttrs { + + if inputAttr == "" { + return nil, fmt.Errorf("input attribute is empty") + } + if strings.Contains(inputAttr, "PR") { outputRule.PlatformRegion = strings.Split(inputAttr, "=")[1] } @@ -33,13 +42,16 @@ func (g* SimpleParser) Parse(ruleEntry string) *Rule { outputRule.HyperscalerRegion = strings.Split(inputAttr, "=")[1] } } - } if len(outputInputPart) > 1 { outputAttrs := strings.Split(outputInputPart[1], ",") for _, outputAttr := range outputAttrs { + if outputAttr == "" { + return nil, fmt.Errorf("output attribute is empty") + } + if outputAttr == "S" { outputRule.Shared = true } else if outputAttr == "EU" { @@ -48,5 +60,5 @@ func (g* SimpleParser) Parse(ruleEntry string) *Rule { } } - return outputRule + return outputRule, nil } diff --git a/common/hyperscaler/rules/simple_parser_test.go b/common/hyperscaler/rules/simple_parser_test.go index 821f72d3a7..6dcfa85940 100644 --- a/common/hyperscaler/rules/simple_parser_test.go +++ b/common/hyperscaler/rules/simple_parser_test.go @@ -3,7 +3,6 @@ package rules import "testing" func TestParser(t *testing.T) { - - ParserTest(t, &SimpleParser{}) - + ParserHappyPathTest(t, &SimpleParser{}) + ParserValidationTest(t, &SimpleParser{}) } \ No newline at end of file