Repository: Shopify/sarama
Pull request #1541 (2019-11-17 20:54:09)

add String, (Un)MarshalText for acl types.
Sometimes it's useful converting the constants of the acl types from/to text.
The text representation is the name without the prefix.
When converting from text to the types the string will be normalized to catch most variants.

d1egoaz: @izolight it seems you'll need to update the `map` declaration `mapping := map[AclOperation]string{`

![image](https://user-images.githubusercontent.com/950087/72820876-a9f07080-3c3d-11ea-9980-1edae77677d7.png)

dnwe: @izolight hiya — just wondering if you think you'll have time to resume work on this PR?
izolight: I updated it so that the pointers are now dereferenced in the function.
probot-shopify[bot]: Thank you for your contribution! However, this pull request has not had any activity in the past 90 days and will be closed in 30 days if no updates occur.
If you believe the changes are still valid then please verify your branch has no conflicts with master and rebase if needed. If you are awaiting a (re-)review then please let us know.


Patch:
diff --git a/acl_types.go b/acl_types.go
index c10ad7b..f3e6e79 100644
--- a/acl_types.go
+++ b/acl_types.go
@@ -1,5 +1,10 @@
 package sarama
 
+import (
+	"fmt"
+	"strings"
+)
+
 type (
 	AclOperation int
 
@@ -27,6 +32,61 @@ const (
 	AclOperationIdempotentWrite
 )
 
+func (a *AclOperation) String() string {
+	mapping := map[AclOperation]string{
+		AclOperationUnknown:         "Unknown",
+		AclOperationAny:             "Any",
+		AclOperationAll:             "All",
+		AclOperationRead:            "Read",
+		AclOperationWrite:           "Write",
+		AclOperationCreate:          "Create",
+		AclOperationDelete:          "Delete",
+		AclOperationAlter:           "Alter",
+		AclOperationDescribe:        "Describe",
+		AclOperationClusterAction:   "ClusterAction",
+		AclOperationDescribeConfigs: "DescribeConfigs",
+		AclOperationAlterConfigs:    "AlterConfigs",
+		AclOperationIdempotentWrite: "IdempotentWrite",
+	}
+	s, ok := mapping[*a]
+	if !ok {
+		s = mapping[AclOperationUnknown]
+	}
+	return s
+}
+
+//MarshalText returns the text form of the AclOperation (name without prefix)
+func (a *AclOperation) MarshalText() ([]byte, error) {
+	return []byte(a.String()), nil
+}
+
+//UnmarshalText takes a text reprentation of the operation and converts it to an AclOperation
+func (a *AclOperation) UnmarshalText(text []byte) error {
+	normalized := strings.ToLower(string(text))
+	mapping := map[string]AclOperation{
+		"unknown":         AclOperationUnknown,
+		"any":             AclOperationAny,
+		"all":             AclOperationAll,
+		"read":            AclOperationRead,
+		"write":           AclOperationWrite,
+		"create":          AclOperationCreate,
+		"delete":          AclOperationDelete,
+		"alter":           AclOperationAlter,
+		"describe":        AclOperationDescribe,
+		"clusteraction":   AclOperationClusterAction,
+		"describeconfigs": AclOperationDescribeConfigs,
+		"alterconfigs":    AclOperationAlterConfigs,
+		"idempotentwrite": AclOperationIdempotentWrite,
+	}
+	ao, ok := mapping[normalized]
+	if !ok {
+		*a = AclOperationUnknown
+		return fmt.Errorf("no acl operation with name %s", normalized)
+	}
+	*a = ao
+	return nil
+}
+
 // ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/acl/AclPermissionType.java
 const (
 	AclPermissionUnknown AclPermissionType = iota
@@ -35,6 +95,44 @@ const (
 	AclPermissionAllow
 )
 
+func (a *AclPermissionType) String() string {
+	mapping := map[AclPermissionType]string{
+		AclPermissionUnknown: "Unknown",
+		AclPermissionAny:     "Any",
+		AclPermissionDeny:    "Deny",
+		AclPermissionAllow:   "Allow",
+	}
+	s, ok := mapping[*a]
+	if !ok {
+		s = mapping[AclPermissionUnknown]
+	}
+	return s
+}
+
+//MarshalText returns the text form of the AclPermissionType (name without prefix)
+func (a *AclPermissionType) MarshalText() ([]byte, error) {
+	return []byte(a.String()), nil
+}
+
+//UnmarshalText takes a text reprentation of the permission type and converts it to an AclPermissionType
+func (a *AclPermissionType) UnmarshalText(text []byte) error {
+	normalized := strings.ToLower(string(text))
+	mapping := map[string]AclPermissionType{
+		"unknown": AclPermissionUnknown,
+		"any":     AclPermissionAny,
+		"deny":    AclPermissionDeny,
+		"allow":   AclPermissionAllow,
+	}
+
+	apt, ok := mapping[normalized]
+	if !ok {
+		*a = AclPermissionUnknown
+		return fmt.Errorf("no acl permission with name %s", normalized)
+	}
+	*a = apt
+	return nil
+}
+
 // ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/resource/ResourceType.java
 const (
 	AclResourceUnknown AclResourceType = iota
@@ -45,6 +143,48 @@ const (
 	AclResourceTransactionalID
 )
 
+func (a *AclResourceType) String() string {
+	mapping := map[AclResourceType]string{
+		AclResourceUnknown:         "Unknown",
+		AclResourceAny:             "Any",
+		AclResourceTopic:           "Topic",
+		AclResourceGroup:           "Group",
+		AclResourceCluster:         "Cluster",
+		AclResourceTransactionalID: "TransactionalID",
+	}
+	s, ok := mapping[*a]
+	if !ok {
+		s = mapping[AclResourceUnknown]
+	}
+	return s
+}
+
+//MarshalText returns the text form of the AclResourceType (name without prefix)
+func (a *AclResourceType) MarshalText() ([]byte, error) {
+	return []byte(a.String()), nil
+}
+
+//UnmarshalText takes a text reprentation of the resource type and converts it to an AclResourceType
+func (a *AclResourceType) UnmarshalText(text []byte) error {
+	normalized := strings.ToLower(string(text))
+	mapping := map[string]AclResourceType{
+		"unknown":         AclResourceUnknown,
+		"any":             AclResourceAny,
+		"topic":           AclResourceTopic,
+		"group":           AclResourceGroup,
+		"cluster":         AclResourceCluster,
+		"transactionalid": AclResourceTransactionalID,
+	}
+
+	art, ok := mapping[normalized]
+	if !ok {
+		*a = AclResourceUnknown
+		return fmt.Errorf("no acl resource with name %s", normalized)
+	}
+	*a = art
+	return nil
+}
+
 // ref: https://github.com/apache/kafka/blob/trunk/clients/src/main/java/org/apache/kafka/common/resource/PatternType.java
 const (
 	AclPatternUnknown AclResourcePatternType = iota
@@ -53,3 +193,43 @@ const (
 	AclPatternLiteral
 	AclPatternPrefixed
 )
+
+func (a *AclResourcePatternType) String() string {
+	mapping := map[AclResourcePatternType]string{
+		AclPatternUnknown:  "Unknown",
+		AclPatternAny:      "Any",
+		AclPatternMatch:    "Match",
+		AclPatternLiteral:  "Literal",
+		AclPatternPrefixed: "Prefixed",
+	}
+	s, ok := mapping[*a]
+	if !ok {
+		s = mapping[AclPatternUnknown]
+	}
+	return s
+}
+
+//MarshalText returns the text form of the AclResourcePatternType (name without prefix)
+func (a *AclResourcePatternType) MarshalText() ([]byte, error) {
+	return []byte(a.String()), nil
+}
+
+//UnmarshalText takes a text reprentation of the resource pattern type and converts it to an AclResourcePatternType
+func (a *AclResourcePatternType) UnmarshalText(text []byte) error {
+	normalized := strings.ToLower(string(text))
+	mapping := map[string]AclResourcePatternType{
+		"unknown":  AclPatternUnknown,
+		"any":      AclPatternAny,
+		"match":    AclPatternMatch,
+		"literal":  AclPatternLiteral,
+		"prefixed": AclPatternPrefixed,
+	}
+
+	arpt, ok := mapping[normalized]
+	if !ok {
+		*a = AclPatternUnknown
+		return fmt.Errorf("no acl resource pattern with name %s", normalized)
+	}
+	*a = arpt
+	return nil
+}


Test patch:
diff --git a/acl_types_test.go b/acl_types_test.go
new file mode 100644
index 0000000..295b7e2
--- /dev/null
+++ b/acl_types_test.go
@@ -0,0 +1,71 @@
+package sarama
+
+import (
+	"testing"
+)
+
+func TestAclOperationTextMarshal(t *testing.T) {
+	for i := AclOperationUnknown; i <= AclOperationIdempotentWrite; i++ {
+		text, err := i.MarshalText()
+		if err != nil {
+			t.Errorf("couldn't marshal %d to text: %s", i, err)
+		}
+		var got AclOperation
+		err = got.UnmarshalText(text)
+		if err != nil {
+			t.Errorf("couldn't unmarshal %s to acl operation: %s", text, err)
+		}
+		if got != i {
+			t.Errorf("got %d, want %d", got, i)
+		}
+	}
+}
+
+func TestAclPermissionTypeTextMarshal(t *testing.T) {
+	for i := AclPermissionUnknown; i <= AclPermissionAllow; i++ {
+		text, err := i.MarshalText()
+		if err != nil {
+			t.Errorf("couldn't marshal %d to text: %s", i, err)
+		}
+		var got AclPermissionType
+		err = got.UnmarshalText(text)
+		if err != nil {
+			t.Errorf("couldn't unmarshal %s to acl permission: %s", text, err)
+		}
+		if got != i {
+			t.Errorf("got %d, want %d", got, i)
+		}
+	}
+}
+func TestAclResourceTypeTextMarshal(t *testing.T) {
+	for i := AclResourceUnknown; i <= AclResourceTransactionalID; i++ {
+		text, err := i.MarshalText()
+		if err != nil {
+			t.Errorf("couldn't marshal %d to text: %s", i, err)
+		}
+		var got AclResourceType
+		err = got.UnmarshalText(text)
+		if err != nil {
+			t.Errorf("couldn't unmarshal %s to acl resource: %s", text, err)
+		}
+		if got != i {
+			t.Errorf("got %d, want %d", got, i)
+		}
+	}
+}
+func TestAclResourcePatternTypeTextMarshal(t *testing.T) {
+	for i := AclPatternUnknown; i <= AclPatternPrefixed; i++ {
+		text, err := i.MarshalText()
+		if err != nil {
+			t.Errorf("couldn't marshal %d to text: %s", i, err)
+		}
+		var got AclResourcePatternType
+		err = got.UnmarshalText(text)
+		if err != nil {
+			t.Errorf("couldn't unmarshal %s to acl resource pattern: %s", text, err)
+		}
+		if got != i {
+			t.Errorf("got %d, want %d", got, i)
+		}
+	}
+}