Skip to content

Commit

Permalink
#3 added resource pattern filter to match resource patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
Krishnakant C authored and Krishnakant C committed Sep 10, 2024
1 parent b00fe84 commit d915481
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 0 deletions.
3 changes: 3 additions & 0 deletions pkg/resource/pattern_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const (
PREFIXED // 4
)

// patternTypeCount represents the total number of PatternType constants.
// const patternTypeCount = iota

// String returns the string representation of the PatternType.
func (p PatternType) String() string {
names := [...]string{
Expand Down
68 changes: 68 additions & 0 deletions pkg/resource/resource_pattern.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package resource

import (
"errors"
"fmt"
)

// ResourcePattern represents a pattern used by ACLs to match zero or more resources.
type ResourcePattern struct {
resourceType ResourceType
name string
patternType PatternType
}

const (
// WILDCARD_RESOURCE is a special literal resource name that corresponds to 'all resources of a certain type'.
WILDCARD_RESOURCE = "*"
)

// NewResourcePattern creates a new ResourcePattern using the supplied parameters.
func NewResourcePattern(resourceType ResourceType, name string, patternType PatternType) (*ResourcePattern, error) {
if resourceType == UNRECOGNIZED {
return nil, errors.New("resourceType cannot be UNRECOGNIZED")
}
if patternType == UNKNOWN {
return nil, errors.New("patternType cannot be UNKNOWN")
}
return &ResourcePattern{
resourceType: resourceType,
name: name,
patternType: patternType,
}, nil
}

// ResourceType returns the specific resource type this pattern matches.
func (rp *ResourcePattern) ResourceType() ResourceType {
return rp.resourceType
}

// Name returns the resource name.
func (rp *ResourcePattern) Name() string {
return rp.name
}

// PatternType returns the resource pattern type.
func (rp *ResourcePattern) PatternType() PatternType {
return rp.patternType
}

// ToFilter returns a filter which matches only this pattern.
func (rp *ResourcePattern) ToFilter() (*ResourcePatternFilter, error) {
return NewResourcePatternFilter(rp.resourceType, rp.name, rp.patternType)
}

// IsUnknown returns true if this ResourcePattern has any UNKNOWN components.
func (rp *ResourcePattern) IsUnknown() bool {
return rp.resourceType == UNRECOGNIZED || rp.patternType == UNKNOWN
}

// String returns a string representation of the ResourcePattern.
func (p *ResourcePattern) String() string {
return fmt.Sprintf("ResourcePattern{resourceType=%s, name=%q, patternType=%s}", p.resourceType.String(), p.name, p.patternType.String())
}

// Equals checks if two ResourcePatterns are equal.
func (rp *ResourcePattern) Equals(other *ResourcePattern) bool {
return other != nil && rp.resourceType == other.resourceType && rp.name == other.name && rp.patternType == other.patternType
}
121 changes: 121 additions & 0 deletions pkg/resource/resource_pattern_filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package resource

import (
"errors"
"fmt"
"strings"
)

// ResourcePatternFilter represents a filter that can match ResourcePattern.
type ResourcePatternFilter struct {
resourceType ResourceType
name string
patternType PatternType
}

// NewResourcePatternFilter creates a new ResourcePatternFilter.
func NewResourcePatternFilter(resourceType ResourceType, name string, patternType PatternType) (*ResourcePatternFilter, error) {
if resourceType == UNRECOGNIZED {
return nil, errors.New("resourceType cannot be UNRECOGNIZED")
}
if patternType == UNKNOWN {
return nil, errors.New("patternType cannot be UNKNOWN")
}
return &ResourcePatternFilter{
resourceType: resourceType,
name: name,
patternType: patternType,
}, nil
}

// AnyResourcePatternFilter creates a filter that matches any resource pattern.
func AnyResourcePatternFilter() *ResourcePatternFilter {
filter, _ := NewResourcePatternFilter(ALL_RESOURCES, "", ANY)
return filter
}

// IsUnknown checks if the filter has any UNKNOWN components.
func (f *ResourcePatternFilter) IsUnknown() bool {
return f.resourceType == UNRECOGNIZED || f.patternType == UNKNOWN
}

// ResourceType returns the specific resource type this pattern matches.
func (f *ResourcePatternFilter) ResourceType() ResourceType {
return f.resourceType
}

// Name returns the resource name.
func (f *ResourcePatternFilter) Name() string {
return f.name
}

// PatternType returns the resource pattern type.
func (f *ResourcePatternFilter) PatternType() PatternType {
return f.patternType
}

// Matches checks if the filter matches the given ResourcePattern.
func (f *ResourcePatternFilter) Matches(pattern *ResourcePattern) bool {
if pattern == nil {
return false
}

if f.resourceType != ALL_RESOURCES && f.resourceType != pattern.resourceType {
return false
}

if f.patternType != ANY && f.patternType != MATCH && f.patternType != pattern.patternType {
return false
}

if f.name == "" {
return true
}

return f.nameMatches(pattern)
}

// nameMatches checks if the name matches based on the pattern type.
func (f *ResourcePatternFilter) nameMatches(pattern *ResourcePattern) bool {
switch {
case f.patternType == ANY:
return true
case f.patternType == pattern.patternType:
return f.name == pattern.name || f.name == WILDCARD_RESOURCE
case f.patternType == PREFIXED:
// Match if the pattern name starts with the filter's name
return strings.HasPrefix(pattern.name, f.name)
case pattern.patternType == LITERAL:
return f.name == pattern.name || pattern.name == WILDCARD_RESOURCE
default:
return false // Return false for unsupported pattern types
}
}

// MatchesAtMostOne checks if the filter could only match one pattern.
func (f *ResourcePatternFilter) MatchesAtMostOne() bool {
return f.findIndefiniteField() == ""
}

// findIndefiniteField returns a string describing any indefinite field, or an empty string if none.
func (f *ResourcePatternFilter) findIndefiniteField() string {
switch {
case f.resourceType == ALL_RESOURCES:
return "Resource type is ALL_RESOURCES."
case f.resourceType == UNRECOGNIZED:
return "Resource type is UNRECOGNIZED."
case f.name == "":
return "Resource name is empty."
case f.patternType == MATCH:
return "Resource pattern type is MATCH."
case f.patternType == UNKNOWN:
return "Resource pattern type is UNKNOWN."
default:
return ""
}
}

// String returns a string representation of the ResourcePatternFilter.
func (f *ResourcePatternFilter) String() string {
return fmt.Sprintf("ResourcePatternFilter{resourceType=%s, name=%q, patternType=%s}", f.resourceType.String(), f.name, f.patternType.String())
}
100 changes: 100 additions & 0 deletions pkg/resource/resource_pattern_filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package resource

import (
"testing"
)

func TestNewResourcePatternFilter(t *testing.T) {
_, err := NewResourcePatternFilter(UNRECOGNIZED, "topic1", LITERAL)
if err == nil {
t.Errorf("NewResourcePatternFilter should return an error for UNRECOGNIZED resourceType")
}

_, err = NewResourcePatternFilter(TOPIC, "topic1", UNKNOWN)
if err == nil {
t.Errorf("NewResourcePatternFilter should return an error for UNKNOWN patternType")
}

filter, err := NewResourcePatternFilter(TOPIC, "topic1", LITERAL)
if err != nil {
t.Errorf("NewResourcePatternFilter returned an unexpected error: %v", err)
}
if filter == nil {
t.Errorf("NewResourcePatternFilter returned a nil filter")
}
}

func TestAnyResourcePatternFilter(t *testing.T) {
filter := AnyResourcePatternFilter()
if filter.resourceType != ALL_RESOURCES {
t.Errorf("AnyResourcePatternFilter should return a filter with resourceType ALL_RESOURCES")
}
if filter.name != "" {
t.Errorf("AnyResourcePatternFilter should return a filter with empty name")
}
if filter.patternType != ANY {
t.Errorf("AnyResourcePatternFilter should return a filter with patternType ANY")
}
}

func TestResourcePatternFilter_Matches(t *testing.T) {
filter, _ := NewResourcePatternFilter(TOPIC, "payments.received", LITERAL)
pattern, _ := NewResourcePattern(TOPIC, "payments.received", LITERAL)
if !filter.Matches(pattern) {
t.Errorf("ResourcePatternFilter should match the exact pattern")
}

filter, _ = NewResourcePatternFilter(TOPIC, "payments.", PREFIXED)
pattern, _ = NewResourcePattern(TOPIC, "payments.received", LITERAL)
if !filter.Matches(pattern) {
t.Errorf("Expected filter to match the pattern: filter=%s, pattern=%s", filter, pattern)
}

filter, _ = NewResourcePatternFilter(TOPIC, "*", LITERAL)
pattern, _ = NewResourcePattern(TOPIC, "payments.received", LITERAL)
if !filter.Matches(pattern) {
t.Errorf("ResourcePatternFilter should match the wildcard pattern")
}

filter, _ = NewResourcePatternFilter(TOPIC, "payments.received", LITERAL)
pattern, _ = NewResourcePattern(GROUP, "payments.received", LITERAL)
if filter.Matches(pattern) {
t.Errorf("ResourcePatternFilter should not match a pattern with different resourceType")
}

filter, _ = NewResourcePatternFilter(TOPIC, "payments.received", LITERAL)
pattern, _ = NewResourcePattern(TOPIC, "payments.received", PREFIXED)
if filter.Matches(pattern) {
t.Errorf("ResourcePatternFilter should not match a pattern with different patternType")
}
}

func TestResourcePatternFilter_MatchesAtMostOne(t *testing.T) {
filter, _ := NewResourcePatternFilter(TOPIC, "payments.received", LITERAL)
if !filter.MatchesAtMostOne() {
t.Errorf("ResourcePatternFilter should match at most one pattern")
}

filter, _ = NewResourcePatternFilter(ALL_RESOURCES, "payments.received", LITERAL)
if filter.MatchesAtMostOne() {
t.Errorf("ResourcePatternFilter with resourceType ALL_RESOURCES should not match at most one pattern")
}

filter, _ = NewResourcePatternFilter(TOPIC, "", LITERAL)
if filter.MatchesAtMostOne() {
t.Errorf("ResourcePatternFilter with empty name should not match at most one pattern")
}

filter, _ = NewResourcePatternFilter(TOPIC, "payments.received", MATCH)
if filter.MatchesAtMostOne() {
t.Errorf("ResourcePatternFilter with patternType MATCH should not match at most one pattern")
}
}

func TestResourcePatternFilter_String(t *testing.T) {
filter, _ := NewResourcePatternFilter(TOPIC, "payments.received", LITERAL)
expected := `ResourcePatternFilter{resourceType=TOPIC, name="payments.received", patternType=LITERAL}`
if filter.String() != expected {
t.Errorf("ResourcePatternFilter.String() returned unexpected value: %s", filter.String())
}
}
8 changes: 8 additions & 0 deletions pkg/resource/resource_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,11 @@ func (r ResourceType) Code() byte {
func (r ResourceType) IsUnrecognized() bool {
return r == UNRECOGNIZED
}

// String returns the string representation of the ResourceType.
func (r ResourceType) String() string {
if int(r) < len(resourceTypeNames) {
return resourceTypeNames[r]
}
return "UNKNOWN"
}

0 comments on commit d915481

Please sign in to comment.