From 37d5b961f5da8ca2a21ccb565af63faaf84ed2fa Mon Sep 17 00:00:00 2001 From: Antoine Pietri Date: Mon, 22 Jul 2024 22:20:30 +0200 Subject: [PATCH] Add a function to return a policy whose output is the explanation. (#977) Implements #974 --- policy/parser.go | 38 +++++++++++++++++++++++++++++++ policy/parser_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/policy/parser.go b/policy/parser.go index be0d8914..d3e9d156 100644 --- a/policy/parser.go +++ b/policy/parser.go @@ -103,6 +103,22 @@ func (p *Policy) SetMetadata(name string, value any) { p.metadata[name] = value } +// GetExplanationOutputPolicy returns a copy of the policy, except the output of each match block +// is replaced by the expression in the explanation field. +func (p *Policy) GetExplanationOutputPolicy() *Policy { + ep := Policy{ + name: p.name, + semantic: p.semantic, + info: &*p.info, + metadata: p.metadata, + source: &*p.source, + } + if p.rule != nil { + ep.rule = p.rule.getExplanationOutputRule() + } + return &ep +} + // NewRule creates a Rule instance. func NewRule() *Rule { return &Rule{ @@ -165,6 +181,28 @@ func (r *Rule) AddVariable(v *Variable) { r.variables = append(r.variables, v) } +func (r *Rule) getExplanationOutputRule() *Rule { + if r == nil { + return nil + } + er := Rule{ + id: r.id, + description: r.description, + } + for _, variable := range r.variables { + er.variables = append(er.variables, &*variable) + } + for _, match := range r.matches { + em := Match{ + condition: match.condition, + output: match.explanation, + rule: match.rule.getExplanationOutputRule(), + } + er.matches = append(er.matches, &em) + } + return &er +} + // NewVariable creates a variable instance. func NewVariable() *Variable { return &Variable{} diff --git a/policy/parser_test.go b/policy/parser_test.go index 063ceeb1..89912683 100644 --- a/policy/parser_test.go +++ b/policy/parser_test.go @@ -166,3 +166,55 @@ rule: } } } + +func TestGetExplanationOutputPolicy(t *testing.T) { + tst := ` +rule: + match: + - condition: "false" + rule: + match: + - condition: "1 > 2" + output: "false" + explanation: "'bad_inner'" + - output: "true" + explanation: "'good_inner'" + - output: "true" + explanation: "'good_outer'" + ` + + parser, err := NewParser() + if err != nil { + t.Fatalf("NewParser() failed: %v", err) + } + policy, iss := parser.Parse(StringSource(tst, "")) + if iss != nil { + t.Fatalf("Parse() failed: %v", err) + } + + explanationPolicy := policy.GetExplanationOutputPolicy() + + want := "'bad_inner'" + got := explanationPolicy.Rule().Matches()[0].rule.Matches()[0].output.Value + if got != want { + t.Errorf("First inner output = %v, wanted %v", got, want) + } + + want = "1 > 2" + got = explanationPolicy.Rule().Matches()[0].rule.Matches()[0].condition.Value + if got != want { + t.Errorf("First inner condition = %v, wanted %v", got, want) + } + + want = "'good_inner'" + got = explanationPolicy.Rule().Matches()[0].rule.Matches()[1].output.Value + if got != want { + t.Errorf("Second inner output = %v, wanted %v", got, want) + } + + want = "'good_outer'" + got = explanationPolicy.Rule().Matches()[1].output.Value + if got != want { + t.Errorf("Second outer output = %v, wanted %v", got, want) + } +}