From 616b4b71a1bba475df12f11561f4e4c9cfcdee54 Mon Sep 17 00:00:00 2001
From: Leonard Lyubich <leonard@nspcc.ru>
Date: Fri, 18 Jun 2021 15:27:01 +0300
Subject: [PATCH] [#310] *: Implement string converters for enumerations

Implement `String` / `FromString` method pair in all levels of enum
definitions. From now `String()` returns canonical protojson-compatible
values.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
---
 pkg/acl/eacl/enums.go        | 136 +++++++++++++++++++++++++++++++++++
 pkg/acl/eacl/enums_test.go   |  96 +++++++++++++++++++++++++
 pkg/checksum.go              |  44 ++++++++++++
 pkg/checksum_test.go         |  42 +++++++++++
 pkg/netmap/clause.go         |  31 +++++---
 pkg/netmap/clause_test.go    |  12 ++++
 pkg/netmap/helper_test.go    |  36 ++++++++++
 pkg/netmap/node_info.go      |  29 ++++++--
 pkg/netmap/node_info_test.go |  12 ++++
 pkg/netmap/operation.go      |  49 +++++++------
 pkg/netmap/operation_test.go |  18 +++++
 pkg/object/search.go         |  27 +++++++
 pkg/object/search_test.go    |  13 ++++
 pkg/object/type.go           |  33 +++++++--
 pkg/object/type_test.go      |  65 ++++++++++++-----
 v2/acl/grpc/types.go         |  65 +++++++++++++++++
 v2/acl/string.go             | 110 ++++++++++++++++++++++++++++
 v2/netmap/grpc/types.go      |  39 ++++++++++
 v2/netmap/string.go          |  68 ++++++++++++++++++
 v2/object/grpc/types.go      |  26 +++++++
 v2/object/string.go          |  66 +++++++++++------
 v2/refs/grpc/types.go        |  13 ++++
 v2/refs/string.go            |  26 +++++++
 v2/session/grpc/types.go     |  26 +++++++
 v2/session/string.go         |  47 ++++++++++++
 25 files changed, 1053 insertions(+), 76 deletions(-)
 create mode 100644 v2/acl/string.go
 create mode 100644 v2/netmap/string.go
 create mode 100644 v2/refs/string.go
 create mode 100644 v2/session/string.go

diff --git a/pkg/acl/eacl/enums.go b/pkg/acl/eacl/enums.go
index 19fa5b92..39a42531 100644
--- a/pkg/acl/eacl/enums.go
+++ b/pkg/acl/eacl/enums.go
@@ -124,6 +124,32 @@ func ActionFromV2(action v2acl.Action) (a Action) {
 	return a
 }
 
+// String returns string representation of Action.
+//
+// String mapping:
+//  * ActionAllow: ALLOW;
+//  * ActionDeny: DENY;
+//  * ActionUnknown, default: ACTION_UNSPECIFIED.
+func (a Action) String() string {
+	return a.ToV2().String()
+}
+
+// FromString parses Action from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (a *Action) FromString(s string) bool {
+	var g v2acl.Action
+
+	ok := g.FromString(s)
+
+	if ok {
+		*a = ActionFromV2(g)
+	}
+
+	return ok
+}
+
 // ToV2 converts Operation to v2 Operation enum value.
 func (o Operation) ToV2() v2acl.Operation {
 	switch o {
@@ -170,6 +196,37 @@ func OperationFromV2(operation v2acl.Operation) (o Operation) {
 	return o
 }
 
+// String returns string representation of Operation.
+//
+// String mapping:
+//  * OperationGet: GET;
+//  * OperationHead: HEAD;
+//  * OperationPut: PUT;
+//  * OperationDelete: DELETE;
+//  * OperationSearch: SEARCH;
+//  * OperationRange: GETRANGE;
+//  * OperationRangeHash: GETRANGEHASH;
+//  * OperationUnknown, default: OPERATION_UNSPECIFIED.
+func (o Operation) String() string {
+	return o.ToV2().String()
+}
+
+// FromString parses Operation from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (o *Operation) FromString(s string) bool {
+	var g v2acl.Operation
+
+	ok := g.FromString(s)
+
+	if ok {
+		*o = OperationFromV2(g)
+	}
+
+	return ok
+}
+
 // ToV2 converts Role to v2 Role enum value.
 func (r Role) ToV2() v2acl.Role {
 	switch r {
@@ -200,6 +257,33 @@ func RoleFromV2(role v2acl.Role) (r Role) {
 	return r
 }
 
+// String returns string representation of Role.
+//
+// String mapping:
+//  * RoleUser: USER;
+//  * RoleSystem: SYSTEM;
+//  * RoleOthers: OTHERS;
+//  * RoleUnknown, default: ROLE_UNKNOWN.
+func (r Role) String() string {
+	return r.ToV2().String()
+}
+
+// FromString parses Role from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (r *Role) FromString(s string) bool {
+	var g v2acl.Role
+
+	ok := g.FromString(s)
+
+	if ok {
+		*r = RoleFromV2(g)
+	}
+
+	return ok
+}
+
 // ToV2 converts Match to v2 MatchType enum value.
 func (m Match) ToV2() v2acl.MatchType {
 	switch m {
@@ -226,6 +310,32 @@ func MatchFromV2(match v2acl.MatchType) (m Match) {
 	return m
 }
 
+// String returns string representation of Match.
+//
+// String mapping:
+//  * MatchStringEqual: STRING_EQUAL;
+//  * MatchStringNotEqual: STRING_NOT_EQUAL;
+//  * MatchUnknown, default: MATCH_TYPE_UNSPECIFIED.
+func (m Match) String() string {
+	return m.ToV2().String()
+}
+
+// FromString parses Match from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (m *Match) FromString(s string) bool {
+	var g v2acl.MatchType
+
+	ok := g.FromString(s)
+
+	if ok {
+		*m = MatchFromV2(g)
+	}
+
+	return ok
+}
+
 // ToV2 converts FilterHeaderType to v2 HeaderType enum value.
 func (h FilterHeaderType) ToV2() v2acl.HeaderType {
 	switch h {
@@ -251,3 +361,29 @@ func FilterHeaderTypeFromV2(header v2acl.HeaderType) (h FilterHeaderType) {
 
 	return h
 }
+
+// String returns string representation of FilterHeaderType.
+//
+// String mapping:
+//  * HeaderFromRequest: REQUEST;
+//  * HeaderFromObject: OBJECT;
+//  * HeaderTypeUnknown, default: HEADER_UNSPECIFIED.
+func (h FilterHeaderType) String() string {
+	return h.ToV2().String()
+}
+
+// FromString parses FilterHeaderType from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (h *FilterHeaderType) FromString(s string) bool {
+	var g v2acl.HeaderType
+
+	ok := g.FromString(s)
+
+	if ok {
+		*h = FilterHeaderTypeFromV2(g)
+	}
+
+	return ok
+}
diff --git a/pkg/acl/eacl/enums_test.go b/pkg/acl/eacl/enums_test.go
index 11f22240..53528494 100644
--- a/pkg/acl/eacl/enums_test.go
+++ b/pkg/acl/eacl/enums_test.go
@@ -115,3 +115,99 @@ func TestFilterHeaderType(t *testing.T) {
 		require.Equal(t, eacl.FilterHeaderTypeFromV2(v2acl.HeaderTypeObject+1), eacl.HeaderTypeUnknown)
 	})
 }
+
+type enumIface interface {
+	FromString(string) bool
+	String() string
+}
+
+type enumStringItem struct {
+	val enumIface
+	str string
+}
+
+func testEnumStrings(t *testing.T, e enumIface, items []enumStringItem) {
+	for _, item := range items {
+		require.Equal(t, item.str, item.val.String())
+
+		s := item.val.String()
+
+		require.True(t, e.FromString(s), s)
+
+		require.EqualValues(t, item.val, e, item.val)
+	}
+
+	// incorrect strings
+	for _, str := range []string{
+		"some string",
+		"UNSPECIFIED",
+	} {
+		require.False(t, e.FromString(str))
+	}
+}
+
+func TestAction_String(t *testing.T) {
+	toPtr := func(v eacl.Action) *eacl.Action {
+		return &v
+	}
+
+	testEnumStrings(t, new(eacl.Action), []enumStringItem{
+		{val: toPtr(eacl.ActionAllow), str: "ALLOW"},
+		{val: toPtr(eacl.ActionDeny), str: "DENY"},
+		{val: toPtr(eacl.ActionUnknown), str: "ACTION_UNSPECIFIED"},
+	})
+}
+
+func TestRole_String(t *testing.T) {
+	toPtr := func(v eacl.Role) *eacl.Role {
+		return &v
+	}
+
+	testEnumStrings(t, new(eacl.Role), []enumStringItem{
+		{val: toPtr(eacl.RoleUser), str: "USER"},
+		{val: toPtr(eacl.RoleSystem), str: "SYSTEM"},
+		{val: toPtr(eacl.RoleOthers), str: "OTHERS"},
+		{val: toPtr(eacl.RoleUnknown), str: "ROLE_UNSPECIFIED"},
+	})
+}
+
+func TestOperation_String(t *testing.T) {
+	toPtr := func(v eacl.Operation) *eacl.Operation {
+		return &v
+	}
+
+	testEnumStrings(t, new(eacl.Operation), []enumStringItem{
+		{val: toPtr(eacl.OperationGet), str: "GET"},
+		{val: toPtr(eacl.OperationPut), str: "PUT"},
+		{val: toPtr(eacl.OperationHead), str: "HEAD"},
+		{val: toPtr(eacl.OperationDelete), str: "DELETE"},
+		{val: toPtr(eacl.OperationSearch), str: "SEARCH"},
+		{val: toPtr(eacl.OperationRange), str: "GETRANGE"},
+		{val: toPtr(eacl.OperationRangeHash), str: "GETRANGEHASH"},
+		{val: toPtr(eacl.OperationUnknown), str: "OPERATION_UNSPECIFIED"},
+	})
+}
+
+func TestMatch_String(t *testing.T) {
+	toPtr := func(v eacl.Match) *eacl.Match {
+		return &v
+	}
+
+	testEnumStrings(t, new(eacl.Match), []enumStringItem{
+		{val: toPtr(eacl.MatchStringEqual), str: "STRING_EQUAL"},
+		{val: toPtr(eacl.MatchStringNotEqual), str: "STRING_NOT_EQUAL"},
+		{val: toPtr(eacl.MatchUnknown), str: "MATCH_TYPE_UNSPECIFIED"},
+	})
+}
+
+func TestFilterHeaderType_String(t *testing.T) {
+	toPtr := func(v eacl.FilterHeaderType) *eacl.FilterHeaderType {
+		return &v
+	}
+
+	testEnumStrings(t, new(eacl.FilterHeaderType), []enumStringItem{
+		{val: toPtr(eacl.HeaderFromRequest), str: "REQUEST"},
+		{val: toPtr(eacl.HeaderFromObject), str: "OBJECT"},
+		{val: toPtr(eacl.HeaderTypeUnknown), str: "HEADER_UNSPECIFIED"},
+	})
+}
diff --git a/pkg/checksum.go b/pkg/checksum.go
index 5d244b9e..b02ae0d4 100644
--- a/pkg/checksum.go
+++ b/pkg/checksum.go
@@ -152,3 +152,47 @@ func (c *Checksum) Parse(s string) error {
 
 	return nil
 }
+
+// String returns string representation of ChecksumType.
+//
+// String mapping:
+//  * ChecksumTZ: TZ;
+//  * ChecksumSHA256: SHA256;
+//  * ChecksumUnknown, default: CHECKSUM_TYPE_UNSPECIFIED.
+func (m ChecksumType) String() string {
+	var m2 refs.ChecksumType
+
+	switch m {
+	default:
+		m2 = refs.UnknownChecksum
+	case ChecksumTZ:
+		m2 = refs.TillichZemor
+	case ChecksumSHA256:
+		m2 = refs.SHA256
+	}
+
+	return m2.String()
+}
+
+// FromString parses ChecksumType from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (m *ChecksumType) FromString(s string) bool {
+	var g refs.ChecksumType
+
+	ok := g.FromString(s)
+
+	if ok {
+		switch g {
+		default:
+			*m = ChecksumUnknown
+		case refs.TillichZemor:
+			*m = ChecksumTZ
+		case refs.SHA256:
+			*m = ChecksumSHA256
+		}
+	}
+
+	return ok
+}
diff --git a/pkg/checksum_test.go b/pkg/checksum_test.go
index e0508b80..b5a06008 100644
--- a/pkg/checksum_test.go
+++ b/pkg/checksum_test.go
@@ -131,3 +131,45 @@ func TestNewChecksum(t *testing.T) {
 		require.Nil(t, chsV2.GetSum())
 	})
 }
+
+type enumIface interface {
+	FromString(string) bool
+	String() string
+}
+
+type enumStringItem struct {
+	val enumIface
+	str string
+}
+
+func testEnumStrings(t *testing.T, e enumIface, items []enumStringItem) {
+	for _, item := range items {
+		require.Equal(t, item.str, item.val.String())
+
+		s := item.val.String()
+
+		require.True(t, e.FromString(s), s)
+
+		require.EqualValues(t, item.val, e, item.val)
+	}
+
+	// incorrect strings
+	for _, str := range []string{
+		"some string",
+		"undefined",
+	} {
+		require.False(t, e.FromString(str))
+	}
+}
+
+func TestChecksumType_String(t *testing.T) {
+	toPtr := func(v ChecksumType) *ChecksumType {
+		return &v
+	}
+
+	testEnumStrings(t, new(ChecksumType), []enumStringItem{
+		{val: toPtr(ChecksumTZ), str: "TZ"},
+		{val: toPtr(ChecksumSHA256), str: "SHA256"},
+		{val: toPtr(ChecksumUnknown), str: "CHECKSUM_TYPE_UNSPECIFIED"},
+	})
+}
diff --git a/pkg/netmap/clause.go b/pkg/netmap/clause.go
index 37ea3a00..d2b5c40c 100644
--- a/pkg/netmap/clause.go
+++ b/pkg/netmap/clause.go
@@ -42,13 +42,28 @@ func (c Clause) ToV2() netmap.Clause {
 	}
 }
 
+// String returns string representation of Clause.
+//
+// String mapping:
+//  * ClauseDistinct: DISTINCT;
+//  * ClauseSame: SAME;
+//  * ClauseUnspecified, default: CLAUSE_UNSPECIFIED.
 func (c Clause) String() string {
-	switch c {
-	default:
-		return "CLAUSE_UNSPECIFIED"
-	case ClauseDistinct:
-		return "DISTINCT"
-	case ClauseSame:
-		return "SAME"
-	}
+	return c.ToV2().String()
+}
+
+// FromString parses Clause from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (c *Clause) FromString(s string) bool {
+	var g netmap.Clause
+
+	ok := g.FromString(s)
+
+	if ok {
+		*c = ClauseFromV2(g)
+	}
+
+	return ok
 }
diff --git a/pkg/netmap/clause_test.go b/pkg/netmap/clause_test.go
index b30d89bc..6ce5e828 100644
--- a/pkg/netmap/clause_test.go
+++ b/pkg/netmap/clause_test.go
@@ -29,3 +29,15 @@ func TestClauseFromV2(t *testing.T) {
 		require.Equal(t, item.cV2, item.c.ToV2())
 	}
 }
+
+func TestClause_String(t *testing.T) {
+	toPtr := func(v Clause) *Clause {
+		return &v
+	}
+
+	testEnumStrings(t, new(Clause), []enumStringItem{
+		{val: toPtr(ClauseDistinct), str: "DISTINCT"},
+		{val: toPtr(ClauseSame), str: "SAME"},
+		{val: toPtr(ClauseUnspecified), str: "CLAUSE_UNSPECIFIED"},
+	})
+}
diff --git a/pkg/netmap/helper_test.go b/pkg/netmap/helper_test.go
index 35df5b54..73b82ae9 100644
--- a/pkg/netmap/helper_test.go
+++ b/pkg/netmap/helper_test.go
@@ -1,5 +1,11 @@
 package netmap
 
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
 func newFilter(name string, k, v string, op Operation, fs ...*Filter) *Filter {
 	f := NewFilter()
 	f.SetName(name)
@@ -55,3 +61,33 @@ func getTestNode(props ...string) *Node {
 	}
 	return &Node{AttrMap: m}
 }
+
+type enumIface interface {
+	FromString(string) bool
+	String() string
+}
+
+type enumStringItem struct {
+	val enumIface
+	str string
+}
+
+func testEnumStrings(t *testing.T, e enumIface, items []enumStringItem) {
+	for _, item := range items {
+		require.Equal(t, item.str, item.val.String())
+
+		s := item.val.String()
+
+		require.True(t, e.FromString(s), s)
+
+		require.EqualValues(t, item.val, e, item.val)
+	}
+
+	// incorrect strings
+	for _, str := range []string{
+		"some string",
+		"undefined",
+	} {
+		require.False(t, e.FromString(str))
+	}
+}
diff --git a/pkg/netmap/node_info.go b/pkg/netmap/node_info.go
index cd813586..35f16179 100644
--- a/pkg/netmap/node_info.go
+++ b/pkg/netmap/node_info.go
@@ -176,15 +176,30 @@ func (s NodeState) ToV2() netmap.NodeState {
 	}
 }
 
+// String returns string representation of NodeState.
+//
+// String mapping:
+//  * NodeStateOnline: ONLINE;
+//  * NodeStateOffline: OFFLINE;
+//  * default: UNSPECIFIED.
 func (s NodeState) String() string {
-	switch s {
-	default:
-		return "STATE_UNSPECIFIED"
-	case NodeStateOffline:
-		return "OFFLINE"
-	case NodeStateOnline:
-		return "ONLINE"
+	return s.ToV2().String()
+}
+
+// FromString parses NodeState from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (s *NodeState) FromString(str string) bool {
+	var g netmap.NodeState
+
+	ok := g.FromString(str)
+
+	if ok {
+		*s = NodeStateFromV2(g)
 	}
+
+	return ok
 }
 
 // NewNodeAttribute creates and returns new NodeAttribute instance.
diff --git a/pkg/netmap/node_info_test.go b/pkg/netmap/node_info_test.go
index e380561d..5d09a593 100644
--- a/pkg/netmap/node_info_test.go
+++ b/pkg/netmap/node_info_test.go
@@ -240,3 +240,15 @@ func TestNewNodeInfo(t *testing.T) {
 		require.EqualValues(t, netmap.UnspecifiedState, niV2.GetState())
 	})
 }
+
+func TestNodeState_String(t *testing.T) {
+	toPtr := func(v NodeState) *NodeState {
+		return &v
+	}
+
+	testEnumStrings(t, new(NodeState), []enumStringItem{
+		{val: toPtr(NodeStateOnline), str: "ONLINE"},
+		{val: toPtr(NodeStateOffline), str: "OFFLINE"},
+		{val: toPtr(0), str: "UNSPECIFIED"},
+	})
+}
diff --git a/pkg/netmap/operation.go b/pkg/netmap/operation.go
index dd388ff6..af3e042a 100644
--- a/pkg/netmap/operation.go
+++ b/pkg/netmap/operation.go
@@ -83,25 +83,34 @@ func (op Operation) ToV2() netmap.Operation {
 	}
 }
 
+// String returns string representation of Operation.
+//
+// String mapping:
+//  * OpNE: NE;
+//  * OpEQ: EQ;
+//  * OpLT: LT;
+//  * OpLE: LE;
+//  * OpGT: GT;
+//  * OpGE: GE;
+//  * OpAND: AND;
+//  * OpOR: OR;
+//  * default: OPERATION_UNSPECIFIED.
 func (op Operation) String() string {
-	switch op {
-	default:
-		return "OPERATION_UNSPECIFIED"
-	case OpNE:
-		return "NE"
-	case OpEQ:
-		return "EQ"
-	case OpLT:
-		return "LT"
-	case OpLE:
-		return "LE"
-	case OpGT:
-		return "GT"
-	case OpGE:
-		return "GE"
-	case OpAND:
-		return "AND"
-	case OpOR:
-		return "OR"
-	}
+	return op.ToV2().String()
+}
+
+// FromString parses Operation from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (op *Operation) FromString(s string) bool {
+	var g netmap.Operation
+
+	ok := g.FromString(s)
+
+	if ok {
+		*op = OperationFromV2(g)
+	}
+
+	return ok
 }
diff --git a/pkg/netmap/operation_test.go b/pkg/netmap/operation_test.go
index c60479bb..e8b74e33 100644
--- a/pkg/netmap/operation_test.go
+++ b/pkg/netmap/operation_test.go
@@ -53,3 +53,21 @@ func TestOperationFromV2(t *testing.T) {
 		require.Equal(t, item.opV2, item.op.ToV2())
 	}
 }
+
+func TestOperation_String(t *testing.T) {
+	toPtr := func(v Operation) *Operation {
+		return &v
+	}
+
+	testEnumStrings(t, new(Operation), []enumStringItem{
+		{val: toPtr(OpEQ), str: "EQ"},
+		{val: toPtr(OpNE), str: "NE"},
+		{val: toPtr(OpGT), str: "GT"},
+		{val: toPtr(OpGE), str: "GE"},
+		{val: toPtr(OpLT), str: "LT"},
+		{val: toPtr(OpLE), str: "LE"},
+		{val: toPtr(OpAND), str: "AND"},
+		{val: toPtr(OpOR), str: "OR"},
+		{val: toPtr(0), str: "OPERATION_UNSPECIFIED"},
+	})
+}
diff --git a/pkg/object/search.go b/pkg/object/search.go
index 9622ec26..438d0aa6 100644
--- a/pkg/object/search.go
+++ b/pkg/object/search.go
@@ -48,6 +48,33 @@ func SearchMatchFromV2(t v2object.MatchType) (m SearchMatchType) {
 	return m
 }
 
+// String returns string representation of SearchMatchType.
+//
+// String mapping:
+//  * MatchStringEqual: STRING_EQUAL;
+//  * MatchStringNotEqual: STRING_NOT_EQUAL;
+//  * MatchNotPresent: NOT_PRESENT;
+//  * MatchUnknown, default: MATCH_TYPE_UNSPECIFIED.
+func (m SearchMatchType) String() string {
+	return m.ToV2().String()
+}
+
+// FromString parses SearchMatchType from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (m *SearchMatchType) FromString(s string) bool {
+	var g v2object.MatchType
+
+	ok := g.FromString(s)
+
+	if ok {
+		*m = SearchMatchFromV2(g)
+	}
+
+	return ok
+}
+
 type SearchFilter struct {
 	header filterKey
 	value  fmt.Stringer
diff --git a/pkg/object/search_test.go b/pkg/object/search_test.go
index 62cca9b5..36870bb4 100644
--- a/pkg/object/search_test.go
+++ b/pkg/object/search_test.go
@@ -192,3 +192,16 @@ func TestSearchFiltersEncoding(t *testing.T) {
 		require.Equal(t, fs, fs2)
 	})
 }
+
+func TestSearchMatchType_String(t *testing.T) {
+	toPtr := func(v object.SearchMatchType) *object.SearchMatchType {
+		return &v
+	}
+
+	testEnumStrings(t, new(object.SearchMatchType), []enumStringItem{
+		{val: toPtr(object.MatchStringEqual), str: "STRING_EQUAL"},
+		{val: toPtr(object.MatchStringNotEqual), str: "STRING_NOT_EQUAL"},
+		{val: toPtr(object.MatchNotPresent), str: "NOT_PRESENT"},
+		{val: toPtr(object.MatchUnknown), str: "MATCH_TYPE_UNSPECIFIED"},
+	})
+}
diff --git a/pkg/object/type.go b/pkg/object/type.go
index fde1aaa0..82d7c001 100644
--- a/pkg/object/type.go
+++ b/pkg/object/type.go
@@ -34,13 +34,36 @@ func TypeFromV2(t object.Type) Type {
 	}
 }
 
+// String returns string representation of Type.
+//
+// String mapping:
+//  * TypeTombstone: TOMBSTONE;
+//  * TypeStorageGroup: STORAGE_GROUP;
+//  * TypeRegular, default: REGULAR.
 func (t Type) String() string {
 	return t.ToV2().String()
 }
 
-// TypeFromString parses Type from its string representation.
-func TypeFromString(s string) Type {
-	return TypeFromV2(
-		object.TypeFromString(s),
-	)
+// FromString parses Type from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (t *Type) FromString(s string) bool {
+	var g object.Type
+
+	ok := g.FromString(s)
+
+	if ok {
+		*t = TypeFromV2(g)
+	}
+
+	return ok
+}
+
+// TypeFromString parses Type from its string representation.
+//
+// Deprecated: use FromString method.
+func TypeFromString(s string) (t Type) {
+	t.FromString(s)
+	return
 }
diff --git a/pkg/object/type_test.go b/pkg/object/type_test.go
index 7ade2ef4..cb8a268b 100644
--- a/pkg/object/type_test.go
+++ b/pkg/object/type_test.go
@@ -1,28 +1,29 @@
-package object
+package object_test
 
 import (
 	"testing"
 
-	"github.com/nspcc-dev/neofs-api-go/v2/object"
+	"github.com/nspcc-dev/neofs-api-go/pkg/object"
+	v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
 	"github.com/stretchr/testify/require"
 )
 
 func TestType_ToV2(t *testing.T) {
 	typs := []struct {
-		t  Type
-		t2 object.Type
+		t  object.Type
+		t2 v2object.Type
 	}{
 		{
-			t:  TypeRegular,
-			t2: object.TypeRegular,
+			t:  object.TypeRegular,
+			t2: v2object.TypeRegular,
 		},
 		{
-			t:  TypeTombstone,
-			t2: object.TypeTombstone,
+			t:  object.TypeTombstone,
+			t2: v2object.TypeTombstone,
 		},
 		{
-			t:  TypeStorageGroup,
-			t2: object.TypeStorageGroup,
+			t:  object.TypeStorageGroup,
+			t2: v2object.TypeStorageGroup,
 		},
 	}
 
@@ -31,16 +32,48 @@ func TestType_ToV2(t *testing.T) {
 
 		require.Equal(t, item.t2, t2)
 
-		require.Equal(t, item.t, TypeFromV2(item.t2))
+		require.Equal(t, item.t, object.TypeFromV2(item.t2))
 	}
 }
 
 func TestType_String(t *testing.T) {
-	for _, typ := range []Type{
-		TypeRegular,
-		TypeTombstone,
-		TypeStorageGroup,
+	toPtr := func(v object.Type) *object.Type {
+		return &v
+	}
+
+	testEnumStrings(t, new(object.Type), []enumStringItem{
+		{val: toPtr(object.TypeTombstone), str: "TOMBSTONE"},
+		{val: toPtr(object.TypeStorageGroup), str: "STORAGE_GROUP"},
+		{val: toPtr(object.TypeRegular), str: "REGULAR"},
+	})
+}
+
+type enumIface interface {
+	FromString(string) bool
+	String() string
+}
+
+type enumStringItem struct {
+	val enumIface
+	str string
+}
+
+func testEnumStrings(t *testing.T, e enumIface, items []enumStringItem) {
+	for _, item := range items {
+		require.Equal(t, item.str, item.val.String())
+
+		s := item.val.String()
+
+		require.True(t, e.FromString(s), s)
+
+		require.EqualValues(t, item.val, e, item.val)
+	}
+
+	// incorrect strings
+	for _, str := range []string{
+		"some string",
+		"undefined",
 	} {
-		require.Equal(t, typ, TypeFromString(typ.String()))
+		require.False(t, e.FromString(str))
 	}
 }
diff --git a/v2/acl/grpc/types.go b/v2/acl/grpc/types.go
index bfb28ed5..cc9b48bd 100644
--- a/v2/acl/grpc/types.go
+++ b/v2/acl/grpc/types.go
@@ -150,3 +150,68 @@ func (m *BearerToken_Body_TokenLifetime) SetIat(v uint64) {
 		m.Iat = v
 	}
 }
+
+// FromString parses Action from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *Action) FromString(s string) bool {
+	i, ok := Action_value[s]
+	if ok {
+		*x = Action(i)
+	}
+
+	return ok
+}
+
+// FromString parses Role from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *Role) FromString(s string) bool {
+	i, ok := Role_value[s]
+	if ok {
+		*x = Role(i)
+	}
+
+	return ok
+}
+
+// FromString parses Operation from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *Operation) FromString(s string) bool {
+	i, ok := Operation_value[s]
+	if ok {
+		*x = Operation(i)
+	}
+
+	return ok
+}
+
+// FromString parses MatchType from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *MatchType) FromString(s string) bool {
+	i, ok := MatchType_value[s]
+	if ok {
+		*x = MatchType(i)
+	}
+
+	return ok
+}
+
+// FromString parses HeaderType from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *HeaderType) FromString(s string) bool {
+	i, ok := HeaderType_value[s]
+	if ok {
+		*x = HeaderType(i)
+	}
+
+	return ok
+}
diff --git a/v2/acl/string.go b/v2/acl/string.go
new file mode 100644
index 00000000..1968f5d3
--- /dev/null
+++ b/v2/acl/string.go
@@ -0,0 +1,110 @@
+package acl
+
+import (
+	acl "github.com/nspcc-dev/neofs-api-go/v2/acl/grpc"
+)
+
+// String returns string representation of Action.
+func (x Action) String() string {
+	return ActionToGRPCField(x).String()
+}
+
+// FromString parses Action from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *Action) FromString(s string) bool {
+	var g acl.Action
+
+	ok := g.FromString(s)
+
+	if ok {
+		*x = ActionFromGRPCField(g)
+	}
+
+	return ok
+}
+
+// String returns string representation of Role.
+func (x Role) String() string {
+	return RoleToGRPCField(x).String()
+}
+
+// FromString parses Role from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *Role) FromString(s string) bool {
+	var g acl.Role
+
+	ok := g.FromString(s)
+
+	if ok {
+		*x = RoleFromGRPCField(g)
+	}
+
+	return ok
+}
+
+// String returns string representation of Operation.
+func (x Operation) String() string {
+	return OperationToGRPCField(x).String()
+}
+
+// FromString parses Operation from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *Operation) FromString(s string) bool {
+	var g acl.Operation
+
+	ok := g.FromString(s)
+
+	if ok {
+		*x = OperationFromGRPCField(g)
+	}
+
+	return ok
+}
+
+// String returns string representation of MatchType.
+func (x MatchType) String() string {
+	return MatchTypeToGRPCField(x).String()
+}
+
+// FromString parses MatchType from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *MatchType) FromString(s string) bool {
+	var g acl.MatchType
+
+	ok := g.FromString(s)
+
+	if ok {
+		*x = MatchTypeFromGRPCField(g)
+	}
+
+	return ok
+}
+
+// String returns string representation of HeaderType.
+func (x HeaderType) String() string {
+	return HeaderTypeToGRPCField(x).String()
+}
+
+// FromString parses HeaderType from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *HeaderType) FromString(s string) bool {
+	var g acl.HeaderType
+
+	ok := g.FromString(s)
+
+	if ok {
+		*x = HeaderTypeFromGRPCField(g)
+	}
+
+	return ok
+}
diff --git a/v2/netmap/grpc/types.go b/v2/netmap/grpc/types.go
index d3e2120f..4340d194 100644
--- a/v2/netmap/grpc/types.go
+++ b/v2/netmap/grpc/types.go
@@ -174,3 +174,42 @@ func (x *NetworkInfo) SetMagicNumber(v uint64) {
 		x.MagicNumber = v
 	}
 }
+
+// FromString parses Clause from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *Clause) FromString(s string) bool {
+	i, ok := Clause_value[s]
+	if ok {
+		*x = Clause(i)
+	}
+
+	return ok
+}
+
+// FromString parses Operation from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *Operation) FromString(s string) bool {
+	i, ok := Operation_value[s]
+	if ok {
+		*x = Operation(i)
+	}
+
+	return ok
+}
+
+// FromString parses NodeInfo_State from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *NodeInfo_State) FromString(s string) bool {
+	i, ok := NodeInfo_State_value[s]
+	if ok {
+		*x = NodeInfo_State(i)
+	}
+
+	return ok
+}
diff --git a/v2/netmap/string.go b/v2/netmap/string.go
new file mode 100644
index 00000000..11ab18cb
--- /dev/null
+++ b/v2/netmap/string.go
@@ -0,0 +1,68 @@
+package netmap
+
+import (
+	netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc"
+)
+
+// String returns string representation of Clause.
+func (x Clause) String() string {
+	return ClauseToGRPCMessage(x).String()
+}
+
+// FromString parses Clause from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *Clause) FromString(s string) bool {
+	var g netmap.Clause
+
+	ok := g.FromString(s)
+
+	if ok {
+		*x = ClauseFromGRPCMessage(g)
+	}
+
+	return ok
+}
+
+// String returns string representation of Operation.
+func (x Operation) String() string {
+	return OperationToGRPCMessage(x).String()
+}
+
+// FromString parses Operation from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *Operation) FromString(s string) bool {
+	var g netmap.Operation
+
+	ok := g.FromString(s)
+
+	if ok {
+		*x = OperationFromGRPCMessage(g)
+	}
+
+	return ok
+}
+
+// String returns string representation of NodeState.
+func (x NodeState) String() string {
+	return NodeStateToGRPCMessage(x).String()
+}
+
+// FromString parses NodeState from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *NodeState) FromString(s string) bool {
+	var g netmap.NodeInfo_State
+
+	ok := g.FromString(s)
+
+	if ok {
+		*x = NodeStateFromRPCMessage(g)
+	}
+
+	return ok
+}
diff --git a/v2/object/grpc/types.go b/v2/object/grpc/types.go
index 9d863938..28eafb70 100644
--- a/v2/object/grpc/types.go
+++ b/v2/object/grpc/types.go
@@ -235,3 +235,29 @@ func (m *SplitInfo) SetLink(v *refs.ObjectID) {
 		m.Link = v
 	}
 }
+
+// FromString parses ObjectType from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *ObjectType) FromString(s string) bool {
+	i, ok := ObjectType_value[s]
+	if ok {
+		*x = ObjectType(i)
+	}
+
+	return ok
+}
+
+// FromString parses MatchType from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *MatchType) FromString(s string) bool {
+	i, ok := MatchType_value[s]
+	if ok {
+		*x = MatchType(i)
+	}
+
+	return ok
+}
diff --git a/v2/object/string.go b/v2/object/string.go
index ad82a124..3191e31f 100644
--- a/v2/object/string.go
+++ b/v2/object/string.go
@@ -1,29 +1,55 @@
 package object
 
-const (
-	typeRegularString      = "Regular"
-	typeTombstoneString    = "Tombstone"
-	typeStorageGroupString = "StorageGroup"
+import (
+	object "github.com/nspcc-dev/neofs-api-go/v2/object/grpc"
 )
 
+// String returns string representation of Type.
 func (t Type) String() string {
-	switch t {
-	default:
-		return typeRegularString
-	case TypeTombstone:
-		return typeTombstoneString
-	case TypeStorageGroup:
-		return typeStorageGroupString
-	}
+	return TypeToGRPCField(t).String()
 }
 
-func TypeFromString(s string) Type {
-	switch s {
-	default:
-		return TypeRegular
-	case typeTombstoneString:
-		return TypeTombstone
-	case typeStorageGroupString:
-		return TypeStorageGroup
+// FromString parses Type from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (t *Type) FromString(s string) bool {
+	var g object.ObjectType
+
+	ok := g.FromString(s)
+
+	if ok {
+		*t = TypeFromGRPCField(g)
 	}
+
+	return ok
+}
+
+// TypeFromString converts string to Type.
+//
+// Deprecated: use FromString method.
+func TypeFromString(s string) (t Type) {
+	t.FromString(s)
+	return
+}
+
+// String returns string representation of MatchType.
+func (t MatchType) String() string {
+	return MatchTypeToGRPCField(t).String()
+}
+
+// FromString parses MatchType from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (t *MatchType) FromString(s string) bool {
+	var g object.MatchType
+
+	ok := g.FromString(s)
+
+	if ok {
+		*t = MatchTypeFromGRPCField(g)
+	}
+
+	return ok
 }
diff --git a/v2/refs/grpc/types.go b/v2/refs/grpc/types.go
index b071dc85..22448e53 100644
--- a/v2/refs/grpc/types.go
+++ b/v2/refs/grpc/types.go
@@ -76,3 +76,16 @@ func (m *Signature) SetSign(v []byte) {
 		m.Sign = v
 	}
 }
+
+// FromString parses ChecksumType from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *ChecksumType) FromString(s string) bool {
+	i, ok := ChecksumType_value[s]
+	if ok {
+		*x = ChecksumType(i)
+	}
+
+	return ok
+}
diff --git a/v2/refs/string.go b/v2/refs/string.go
new file mode 100644
index 00000000..b27e07f4
--- /dev/null
+++ b/v2/refs/string.go
@@ -0,0 +1,26 @@
+package refs
+
+import (
+	refs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc"
+)
+
+// String returns string representation of ChecksumType.
+func (t ChecksumType) String() string {
+	return ChecksumTypeToGRPC(t).String()
+}
+
+// FromString parses ChecksumType from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (t *ChecksumType) FromString(s string) bool {
+	var g refs.ChecksumType
+
+	ok := g.FromString(s)
+
+	if ok {
+		*t = ChecksumTypeFromGRPC(g)
+	}
+
+	return ok
+}
diff --git a/v2/session/grpc/types.go b/v2/session/grpc/types.go
index 20f9ee76..cee46384 100644
--- a/v2/session/grpc/types.go
+++ b/v2/session/grpc/types.go
@@ -274,3 +274,29 @@ func (m *ResponseVerificationHeader) SetOrigin(v *ResponseVerificationHeader) {
 		m.Origin = v
 	}
 }
+
+// FromString parses ObjectSessionContext_Verb from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *ObjectSessionContext_Verb) FromString(s string) bool {
+	i, ok := ObjectSessionContext_Verb_value[s]
+	if ok {
+		*x = ObjectSessionContext_Verb(i)
+	}
+
+	return ok
+}
+
+// FromString parses ContainerSessionContext_Verb from a string representation,
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *ContainerSessionContext_Verb) FromString(s string) bool {
+	i, ok := ContainerSessionContext_Verb_value[s]
+	if ok {
+		*x = ContainerSessionContext_Verb(i)
+	}
+
+	return ok
+}
diff --git a/v2/session/string.go b/v2/session/string.go
new file mode 100644
index 00000000..0335ed7f
--- /dev/null
+++ b/v2/session/string.go
@@ -0,0 +1,47 @@
+package session
+
+import (
+	session "github.com/nspcc-dev/neofs-api-go/v2/session/grpc"
+)
+
+// String returns string representation of ObjectSessionVerb.
+func (x ObjectSessionVerb) String() string {
+	return ObjectSessionVerbToGRPCField(x).String()
+}
+
+// FromString parses ObjectSessionVerb from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *ObjectSessionVerb) FromString(s string) bool {
+	var g session.ObjectSessionContext_Verb
+
+	ok := g.FromString(s)
+
+	if ok {
+		*x = ObjectSessionVerbFromGRPCField(g)
+	}
+
+	return ok
+}
+
+// String returns string representation of ContainerSessionVerb.
+func (x ContainerSessionVerb) String() string {
+	return ContainerSessionVerbToGRPCField(x).String()
+}
+
+// FromString parses ContainerSessionVerb from a string representation.
+// It is a reverse action to String().
+//
+// Returns true if s was parsed successfully.
+func (x *ContainerSessionVerb) FromString(s string) bool {
+	var g session.ContainerSessionContext_Verb
+
+	ok := g.FromString(s)
+
+	if ok {
+		*x = ContainerSessionVerbFromGRPCField(g)
+	}
+
+	return ok
+}