From 4b6aa6f3b735446e02593a4d5730146e48e62eee Mon Sep 17 00:00:00 2001
From: Pavel Antonov <antonov@perx.ru>
Date: Tue, 13 Feb 2024 15:10:51 +0400
Subject: [PATCH] WIP

---
 id/{bson_test.go => _bson_test.go} |  18 +-
 id/{json_test.go => _json_test.go} |  18 +-
 id/_space.go                       |  48 ++++
 id/bson.go                         |  32 ---
 id/collection.go                   |  62 +++--
 id/environment.go                  |  64 +++---
 id/error.go                        |  11 +
 id/field.go                        |  70 +++---
 id/id.go                           | 143 ------------
 id/id_test.go                      | 301 ------------------------
 id/item.go                         |  63 +++--
 id/json.go                         |  23 --
 id/object_id.go                    | 102 +++++++++
 id/object_id_test.go               | 306 +++++++++++++++++++++++++
 id/{ => old}/client.go             |   6 +-
 id/{ => old}/organization.go       |   4 +-
 id/{ => old}/revision.go           |   6 +-
 id/{ => old}/role.go               |   6 +-
 id/{ => old}/schema.go             |   6 +-
 id/{ => old}/service.go            |   4 +-
 id/{ => old}/system.go             |   4 +-
 id/{ => old}/user.go               |   4 +-
 id/registry.go                     | 108 +++++++++
 id/space.go                        |  57 +++--
 id/system/system.go                |  68 ++++++
 id/test/object_id_test.go          | 354 +++++++++++++++++++++++++++++
 26 files changed, 1196 insertions(+), 692 deletions(-)
 rename id/{bson_test.go => _bson_test.go} (72%)
 rename id/{json_test.go => _json_test.go} (72%)
 create mode 100644 id/_space.go
 delete mode 100644 id/bson.go
 create mode 100644 id/error.go
 delete mode 100644 id/id.go
 delete mode 100644 id/id_test.go
 delete mode 100644 id/json.go
 create mode 100644 id/object_id.go
 create mode 100644 id/object_id_test.go
 rename id/{ => old}/client.go (93%)
 rename id/{ => old}/organization.go (94%)
 rename id/{ => old}/revision.go (94%)
 rename id/{ => old}/role.go (93%)
 rename id/{ => old}/schema.go (93%)
 rename id/{ => old}/service.go (94%)
 rename id/{ => old}/system.go (88%)
 rename id/{ => old}/user.go (93%)
 create mode 100644 id/registry.go
 create mode 100644 id/system/system.go
 create mode 100644 id/test/object_id_test.go

diff --git a/id/bson_test.go b/id/_bson_test.go
similarity index 72%
rename from id/bson_test.go
rename to id/_bson_test.go
index c8080b5d..43c09e47 100644
--- a/id/bson_test.go
+++ b/id/_bson_test.go
@@ -26,7 +26,7 @@ func TestID_MarshalUnmarshalBSON(t *testing.T) {
 			id:   &ID{Descriptor: &ServiceID{ServiceID: "1"}},
 		},
 		{
-			name: "SpaceID",
+			name: "SpaceId",
 			id:   &ID{Descriptor: &SpaceID{SpaceID: "1"}},
 		},
 		{
@@ -42,24 +42,24 @@ func TestID_MarshalUnmarshalBSON(t *testing.T) {
 			id:   &ID{Descriptor: &RoleID{RoleID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
 		},
 		{
-			name: "CollectionID",
-			id:   &ID{Descriptor: &CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+			name: "CollectionId",
+			id:   &ID{Descriptor: &CollectionId{CollectionID: "1", EnvironmentId: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
 		},
 		{
 			name: "SchemaID",
-			id:   &ID{Descriptor: &SchemaID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+			id:   &ID{Descriptor: &SchemaID{CollectionId: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
 		},
 		{
-			name: "ItemID",
-			id:   &ID{Descriptor: &ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}},
+			name: "ItemId",
+			id:   &ID{Descriptor: &ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}},
 		},
 		{
 			name: "RevisionID",
-			id:   &ID{Descriptor: &RevisionID{RevisionID: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+			id:   &ID{Descriptor: &RevisionID{RevisionID: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
 		},
 		{
-			name: "FieldID",
-			id:   &ID{Descriptor: &FieldID{FieldName: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+			name: "FieldId",
+			id:   &ID{Descriptor: &FieldId{FieldName: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
 		},
 		{
 			name: "SystemID",
diff --git a/id/json_test.go b/id/_json_test.go
similarity index 72%
rename from id/json_test.go
rename to id/_json_test.go
index afe831d4..811df1c0 100644
--- a/id/json_test.go
+++ b/id/_json_test.go
@@ -26,7 +26,7 @@ func TestID_MarshalUnmarshalJSON(t *testing.T) {
 			id:   &ID{Descriptor: &ServiceID{ServiceID: "1"}},
 		},
 		{
-			name: "SpaceID",
+			name: "SpaceId",
 			id:   &ID{Descriptor: &SpaceID{SpaceID: "1"}},
 		},
 		{
@@ -42,24 +42,24 @@ func TestID_MarshalUnmarshalJSON(t *testing.T) {
 			id:   &ID{Descriptor: &RoleID{RoleID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
 		},
 		{
-			name: "CollectionID",
-			id:   &ID{Descriptor: &CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+			name: "CollectionId",
+			id:   &ID{Descriptor: &CollectionId{CollectionID: "1", EnvironmentId: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
 		},
 		{
 			name: "SchemaID",
-			id:   &ID{Descriptor: &SchemaID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+			id:   &ID{Descriptor: &SchemaID{CollectionId: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
 		},
 		{
-			name: "ItemID",
-			id:   &ID{Descriptor: &ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}},
+			name: "ItemId",
+			id:   &ID{Descriptor: &ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}},
 		},
 		{
 			name: "RevisionID",
-			id:   &ID{Descriptor: &RevisionID{RevisionID: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+			id:   &ID{Descriptor: &RevisionID{RevisionID: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
 		},
 		{
-			name: "FieldID",
-			id:   &ID{Descriptor: &FieldID{FieldName: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+			name: "FieldId",
+			id:   &ID{Descriptor: &FieldId{FieldName: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
 		},
 		{
 			name: "SystemID",
diff --git a/id/_space.go b/id/_space.go
new file mode 100644
index 00000000..4b7bff76
--- /dev/null
+++ b/id/_space.go
@@ -0,0 +1,48 @@
+package id
+
+const (
+	Space        = "space"
+	SpacesPrefix = "spaces"
+)
+
+type SpaceID struct {
+	SpaceID string `json:"space_id,omitempty" bson:"space_id,omitempty"`
+}
+
+func (t *SpaceID) Type() string { return Space }
+
+func (t *SpaceID) String() string {
+	return Join(SpacesPrefix, t.SpaceID)
+}
+
+func (t *SpaceID) Map() map[string]any {
+	return map[string]any{
+		"space_id": t.SpaceID,
+		"type":     Space,
+	}
+}
+
+func (t *SpaceID) FromMap(m map[string]any) error {
+	t.SpaceID = m["space_id"].(string)
+	return nil
+}
+
+func (t *SpaceID) Validate() error {
+	if t.SpaceID == "" {
+		return ErrInvalidID
+	}
+	return nil
+}
+
+func parseSpaceID(parts []string) (*SpaceID, error) {
+	var id SpaceID
+	if len(parts) != 2 || parts[0] != SpacesPrefix {
+		return nil, ErrInvalidID
+	}
+
+	id.SpaceID = parts[1]
+	return &id, nil
+}
+func NewSpaceID(id string) *ID {
+	return &ID{Descriptor: &SpaceID{SpaceID: id}}
+}
diff --git a/id/bson.go b/id/bson.go
deleted file mode 100644
index 9d44c5a8..00000000
--- a/id/bson.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package id
-
-import (
-	"git.perx.ru/perxis/perxis-go/pkg/errors"
-	"go.mongodb.org/mongo-driver/bson"
-	"go.mongodb.org/mongo-driver/bson/bsonrw"
-	"go.mongodb.org/mongo-driver/bson/bsontype"
-)
-
-func (id *ID) MarshalBSONValue() (bsontype.Type, []byte, error) {
-	return bson.MarshalValue(id.String())
-}
-
-func (id *ID) UnmarshalBSONValue(btype bsontype.Type, data []byte) error {
-	if btype != bson.TypeString {
-		return errors.New("cannot unmarshal non-string bson value to ID")
-	}
-	dec, err := bson.NewDecoder(bsonrw.NewBSONValueReader(btype, data))
-	if err != nil {
-		return err
-	}
-	var str string
-	if err = dec.Decode(&str); err != nil {
-		return err
-	}
-	t, err := Parse(str)
-	if err != nil {
-		return err
-	}
-	*id = *t
-	return nil
-}
diff --git a/id/collection.go b/id/collection.go
index e0f37585..d74aa5f1 100644
--- a/id/collection.go
+++ b/id/collection.go
@@ -1,60 +1,58 @@
 package id
 
+import "fmt"
+
 const (
 	Collection        = "collection"
 	CollectionsPrefix = "cols"
 )
 
-type CollectionID struct {
-	EnvironmentID
+var _ Descriptor = &CollectionId{}
+
+type CollectionId struct {
+	EnvironmentId
 	CollectionID string `json:"col_id,omitempty" bson:"col_id, omitempty"`
 }
 
-func (t *CollectionID) Type() string { return Collection }
+func (id *CollectionId) New() Descriptor {
+	return &CollectionId{}
+}
 
-func (t *CollectionID) String() string {
-	return Join(t.EnvironmentID.String(), CollectionsPrefix, t.CollectionID)
+func (t *CollectionId) Type() string { return Collection }
+
+func (t *CollectionId) String() string {
+	return Join(t.EnvironmentId.String(), CollectionsPrefix, t.CollectionID)
 }
 
-func (t *CollectionID) ToMap() map[string]any {
-	m := t.EnvironmentID.ToMap()
+func (t *CollectionId) Map() map[string]any {
+	m := t.EnvironmentId.Map()
 	m["col_id"] = t.CollectionID
 	m["type"] = Collection
 	return m
 }
 
-func (t *CollectionID) FromMap(m map[string]any) error {
-	if err := t.EnvironmentID.FromMap(m); err != nil {
+func (id *CollectionId) FromParts(parts []string) error {
+	if len(parts) != 6 || parts[4] != CollectionsPrefix {
+		return ErrInvalid
+	}
+	if err := id.EnvironmentId.FromParts(parts[:4]); err != nil {
 		return err
 	}
-	t.CollectionID = m["col_id"].(string)
+	id.CollectionID = parts[5]
 	return nil
 }
 
-func (t *CollectionID) Validate() error {
-	if t.CollectionID == "" {
-		return ErrInvalidID
+func (id *CollectionId) FromMap(m map[string]any) error {
+	id.CollectionID, _ = m["col_id"].(string)
+	if id.CollectionID == "" {
+		return fmt.Errorf("%w: CollectionID required", ErrInvalid)
 	}
-
-	return t.EnvironmentID.Validate()
+	return id.EnvironmentId.FromMap(m)
 }
 
-func parseCollectionID(parts []string) (*CollectionID, error) {
-	if len(parts) != 6 || parts[4] != CollectionsPrefix {
-		return nil, ErrInvalidID
-	}
-
-	envID, err := parseEnvironmentID(parts[:4])
-	if err != nil {
-		return nil, err
+func (t *CollectionId) Validate() error {
+	if t.CollectionID == "" {
+		return fmt.Errorf("%w: CollectionID required", ErrInvalid)
 	}
-
-	var id CollectionID
-	id.CollectionID = parts[5]
-	id.EnvironmentID = *envID
-	return &id, nil
-}
-
-func NewCollectionID(spaceID, envID, id string) *ID {
-	return &ID{Descriptor: &CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: id}}
+	return t.EnvironmentId.Validate()
 }
diff --git a/id/environment.go b/id/environment.go
index d42df3e6..6c09603d 100644
--- a/id/environment.go
+++ b/id/environment.go
@@ -1,61 +1,59 @@
 package id
 
+import (
+	"fmt"
+)
+
 const (
 	Environment        = "environment"
 	EnvironmentsPrefix = "envs"
 )
 
-type EnvironmentID struct {
-	SpaceID
+type EnvironmentId struct {
+	SpaceId
 	EnvironmentID string `json:"env_id,omitempty" bson:"env_id,omitempty"`
 }
 
-func (t *EnvironmentID) Type() string { return Environment }
+func (id *EnvironmentId) New() Descriptor {
+	return &EnvironmentId{}
+}
+
+func (id *EnvironmentId) Type() string { return "environment" }
 
-func (t *EnvironmentID) String() string {
-	return Join(t.SpaceID.String(), EnvironmentsPrefix, t.EnvironmentID)
+func (id *EnvironmentId) String() string {
+	return Join(id.SpaceId.String(), EnvironmentsPrefix, id.EnvironmentID)
 
 }
 
-func (t *EnvironmentID) ToMap() map[string]any {
-	m := t.SpaceID.ToMap()
-	m["env_id"] = t.EnvironmentID
+func (id *EnvironmentId) Map() map[string]any {
+	m := id.SpaceId.Map()
+	m["env_id"] = id.EnvironmentID
 	m["type"] = Environment
 	return m
 }
 
-func (t *EnvironmentID) FromMap(m map[string]any) error {
-	if err := t.SpaceID.FromMap(m); err != nil {
+func (id *EnvironmentId) FromParts(parts []string) error {
+	if len(parts) != 4 || parts[2] != EnvironmentsPrefix {
+		return ErrInvalid
+	}
+	if err := id.SpaceId.FromParts(parts[:2]); err != nil {
 		return err
 	}
-	t.EnvironmentID = m["env_id"].(string)
+	id.EnvironmentID = parts[3]
 	return nil
 }
 
-func (t *EnvironmentID) Validate() error {
-	if t.EnvironmentID == "" {
-		return ErrInvalidID
+func (id *EnvironmentId) FromMap(m map[string]any) error {
+	id.EnvironmentID, _ = m["env_id"].(string)
+	if id.EnvironmentID == "" {
+		return fmt.Errorf("%w: EnviromentID required", ErrInvalid)
 	}
-
-	return t.SpaceID.Validate()
+	return id.SpaceId.FromMap(m)
 }
 
-func parseEnvironmentID(parts []string) (*EnvironmentID, error) {
-	if len(parts) != 4 || parts[2] != EnvironmentsPrefix {
-		return nil, ErrInvalidID
-	}
-
-	spaceID, err := parseSpaceID(parts[:2])
-	if err != nil {
-		return nil, err
+func (id *EnvironmentId) Validate() error {
+	if id.EnvironmentID == "" {
+		return fmt.Errorf("%w: EnvironmetnID required", ErrInvalid)
 	}
-
-	var id EnvironmentID
-	id.EnvironmentID = parts[3]
-	id.SpaceID = *spaceID
-	return &id, nil
-}
-
-func NewEnvironmentID(spaceID, id string) *ID {
-	return &ID{Descriptor: &EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: id}}
+	return id.SpaceId.Validate()
 }
diff --git a/id/error.go b/id/error.go
new file mode 100644
index 00000000..0c45ebb5
--- /dev/null
+++ b/id/error.go
@@ -0,0 +1,11 @@
+package id
+
+import "errors"
+
+var (
+	ErrInvalid         = errors.New("invalid ObjectId")
+	ErrUnknown         = errors.New("unknown ObjectId type")
+	ErrHandlerNotFound = errors.New("ObjectId handler not found")
+	ErrTypeNotFound    = errors.New("type key not found in map")
+	ErrInvalidType     = errors.New("ObjectId should be a string")
+)
diff --git a/id/field.go b/id/field.go
index ca157755..11c7e270 100644
--- a/id/field.go
+++ b/id/field.go
@@ -1,60 +1,62 @@
 package id
 
+import "fmt"
+
 const (
 	Field        = "field"
 	FieldsPrefix = "fields"
 )
 
-type FieldID struct {
-	ItemID
-	FieldName string `json:"field_name,omitempty" bson:"field_name,omitempty"`
+type FieldId struct {
+	ItemId
+	Field string `json:"field,omitempty" bson:"field,omitempty"`
 }
 
-func (t *FieldID) Type() string { return Field }
+func (i *FieldId) New() Descriptor {
+	return &FieldId{}
+}
 
-func (t *FieldID) String() string {
-	return Join(t.ItemID.String(), FieldsPrefix, t.FieldName)
+func (i *FieldId) Type() string { return Field }
 
-}
+func (i *FieldId) String() string {
+	return Join(i.ItemId.String(), FieldsPrefix, i.Field)
 
-func (t *FieldID) ToMap() map[string]any {
-	m := t.ItemID.ToMap()
-	m["field_name"] = t.FieldName
-	m["type"] = Field
-	return m
 }
 
-func (t *FieldID) FromMap(m map[string]any) error {
-	if err := t.ItemID.FromMap(m); err != nil {
+func (i *FieldId) FromParts(parts []string) error {
+	if len(parts) != 10 || parts[8] != FieldsPrefix {
+		return ErrInvalid
+	}
+	if err := i.ItemId.FromParts(parts[:8]); err != nil {
 		return err
 	}
-	t.FieldName = m["field_name"].(string)
+	i.Field = parts[9]
 	return nil
 }
 
-func (t *FieldID) Validate() error {
-	if t.FieldName == "" {
-		return ErrInvalidID
-	}
-
-	return t.ItemID.Validate()
+func (i *FieldId) Map() map[string]any {
+	m := i.ItemId.Map()
+	m["field"] = i.Field
+	m["type"] = Field
+	return m
 }
 
-func parseFieldID(parts []string) (*FieldID, error) {
-	if len(parts) != 10 || parts[8] != FieldsPrefix {
-		return nil, ErrInvalidID
+func (i *FieldId) FromMap(m map[string]any) error {
+	i.Field = m["field"].(string)
+	if i.Field == "" {
+		return fmt.Errorf("%w: Field required", ErrInvalid)
 	}
 
-	itemID, err := parseItemID(parts[:8])
-	if err != nil {
-		return nil, err
+	if err := i.ItemId.FromMap(m); err != nil {
+		return err
 	}
-
-	var id FieldID
-	id.ItemID = *itemID
-	id.FieldName = parts[9]
-	return &id, nil
+	return nil
 }
-func NewFieldID(spaceID, envID, collID, itemID, id string) *ID {
-	return &ID{Descriptor: &FieldID{ItemID: ItemID{CollectionID: CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: itemID}, FieldName: id}}
+
+func (i *FieldId) Validate() error {
+	if i.Field == "" {
+		return fmt.Errorf("%w: Field required", ErrInvalid)
+	}
+
+	return i.ItemId.Validate()
 }
diff --git a/id/id.go b/id/id.go
deleted file mode 100644
index ca75c29f..00000000
--- a/id/id.go
+++ /dev/null
@@ -1,143 +0,0 @@
-package id
-
-import (
-	"strings"
-
-	"git.perx.ru/perxis/perxis-go/pkg/errors"
-)
-
-const Separator = '/'
-
-var (
-	ErrInvalidID = errors.New("invalid id")
-)
-
-type Descriptor interface {
-	String() string
-	Type() string
-	ToMap() map[string]any
-	FromMap(map[string]any) error
-	Validate() error
-}
-
-type ID struct {
-	Descriptor
-}
-
-func Parse(s string) (*ID, error) {
-	parts := Split(s)
-
-	if id, _ := parseServiceID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseUserID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseOrganizationID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseSpaceID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseEnvironmentID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseClientID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseRoleID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseCollectionID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseSchemaID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseItemID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseRevisionID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseFieldID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	if id, _ := parseSystemID(parts); id != nil {
-		return &ID{Descriptor: id}, nil
-	}
-
-	return nil, ErrInvalidID
-}
-
-func Split(id string) []string {
-	if id[0] != Separator {
-		return nil
-	}
-	return strings.Split(id[1:], string(Separator))
-}
-
-func Join(parts ...string) string {
-	s := strings.Join(parts, string(Separator))
-	if s[0] != Separator {
-		s = string(Separator) + s
-	}
-	return s
-}
-
-func FromMap(m map[string]any) (*ID, error) {
-	if m == nil {
-		return nil, errors.New("nil map")
-	}
-
-	v := new(ID)
-
-	switch m["type"] {
-	case Organization:
-		v.Descriptor = new(OrganizationID)
-	case Service:
-		v.Descriptor = new(ServiceID)
-	case User:
-		v.Descriptor = new(UserID)
-	case Space:
-		v.Descriptor = new(SpaceID)
-	case Environment:
-		v.Descriptor = new(EnvironmentID)
-	case Client:
-		v.Descriptor = new(ClientID)
-	case Role:
-		v.Descriptor = new(RoleID)
-	case Collection:
-		v.Descriptor = new(CollectionID)
-	case Schema:
-		v.Descriptor = new(SchemaID)
-	case Item:
-		v.Descriptor = new(ItemID)
-	case Revision:
-		v.Descriptor = new(RevisionID)
-	case Field:
-		v.Descriptor = new(FieldID)
-	case System:
-		v.Descriptor = new(SystemID)
-	default:
-		return nil, errors.New("unknown type")
-	}
-
-	if err := v.Descriptor.FromMap(m); err != nil {
-		return nil, err
-	}
-
-	return v, nil
-}
diff --git a/id/id_test.go b/id/id_test.go
deleted file mode 100644
index 041f1cdc..00000000
--- a/id/id_test.go
+++ /dev/null
@@ -1,301 +0,0 @@
-package id
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-func Test_ParseID(t *testing.T) {
-	tests := []struct {
-		name      string
-		id        string
-		result    *ID
-		wantError bool
-	}{
-		{
-			name:   "ServiceID",
-			id:     "/services/<service_id>",
-			result: &ID{Descriptor: &ServiceID{ServiceID: "<service_id>"}},
-		},
-		{
-			name:   "UserID",
-			id:     "/users/<user_id>",
-			result: &ID{Descriptor: &UserID{UserID: "<user_id>"}},
-		},
-		{
-			name:   "OrganizationID",
-			id:     "/orgs/<org_id>",
-			result: &ID{Descriptor: &OrganizationID{OrganizationID: "<org_id>"}},
-		},
-		{
-			name:   "SpaceID",
-			id:     "/spaces/<space_id>",
-			result: &ID{Descriptor: &SpaceID{SpaceID: "<space_id>"}},
-		},
-		{
-			name: "ClientID",
-			id:   "/spaces/<space_id>/clients/<client_id>",
-			result: &ID{Descriptor: &ClientID{
-				SpaceID:  SpaceID{SpaceID: "<space_id>"},
-				ClientID: "<client_id>",
-			}},
-		},
-		{
-			name: "RoleID",
-			id:   "/spaces/<space_id>/roles/<role_id>",
-			result: &ID{Descriptor: &RoleID{
-				SpaceID: SpaceID{SpaceID: "<space_id>"},
-				RoleID:  "<role_id>",
-			}},
-		},
-		{
-			name: "EnvironmentID",
-			id:   "/spaces/<space_id>/envs/<env_id>",
-			result: &ID{Descriptor: &EnvironmentID{
-				SpaceID:       SpaceID{SpaceID: "<space_id>"},
-				EnvironmentID: "<env_id>",
-			}},
-		},
-		{
-			name: "CollectionID",
-			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
-			result: &ID{Descriptor: &CollectionID{
-				EnvironmentID: EnvironmentID{
-					SpaceID:       SpaceID{SpaceID: "<space_id>"},
-					EnvironmentID: "<env_id>",
-				},
-				CollectionID: "<collection_id>",
-			}},
-		},
-		{
-			name: "SchemaID",
-			id:   "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>",
-			result: &ID{Descriptor: &SchemaID{
-				EnvironmentID: EnvironmentID{
-					SpaceID:       SpaceID{SpaceID: "<space_id>"},
-					EnvironmentID: "<env_id>",
-				},
-				CollectionID: "<collection_id>",
-			}},
-		},
-		{
-			name: "ItemID",
-			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
-			result: &ID{Descriptor: &ItemID{
-				CollectionID: CollectionID{
-					EnvironmentID: EnvironmentID{
-						SpaceID:       SpaceID{SpaceID: "<space_id>"},
-						EnvironmentID: "<env_id>",
-					},
-					CollectionID: "<collection_id>",
-				},
-				ItemID: "<item_id>",
-			}},
-		},
-		{
-			name: "RevisionID",
-			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>",
-			result: &ID{Descriptor: &RevisionID{
-				ItemID: ItemID{
-					CollectionID: CollectionID{
-						EnvironmentID: EnvironmentID{
-							SpaceID:       SpaceID{SpaceID: "<space_id>"},
-							EnvironmentID: "<env_id>",
-						},
-						CollectionID: "<collection_id>",
-					},
-					ItemID: "<item_id>",
-				},
-				RevisionID: "<rev_id>",
-			}},
-		},
-		{
-			name: "FieldID",
-			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field_name>",
-			result: &ID{Descriptor: &FieldID{
-				ItemID: ItemID{
-					CollectionID: CollectionID{
-						EnvironmentID: EnvironmentID{
-							SpaceID:       SpaceID{SpaceID: "<space_id>"},
-							EnvironmentID: "<env_id>",
-						},
-						CollectionID: "<collection_id>",
-					},
-					ItemID: "<item_id>",
-				},
-				FieldName: "<field_name>",
-			}},
-		},
-		{
-			name:   "SystemID",
-			id:     "/system",
-			result: &ID{Descriptor: &SystemID{}},
-		},
-		{
-			name:      "With error #1: no backslash in the beginning of id",
-			id:        "spaces/<space_id>",
-			result:    nil,
-			wantError: true,
-		},
-		{
-			name:      "With error #2: backslash in the end of id",
-			id:        "/spaces/<space_id>/",
-			result:    nil,
-			wantError: true,
-		},
-		{
-			name:      "With error #3: typo in 'spaces'",
-			id:        "/space/<space_id>",
-			result:    nil,
-			wantError: true,
-		},
-		{
-			name:      "With error #4: no space_id in id",
-			id:        "/spaces",
-			result:    nil,
-			wantError: true,
-		},
-		{
-			name:      "With error #5: multiple backslashes in the end of id",
-			id:        "/spaces/<space_id>///",
-			result:    nil,
-			wantError: true,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			id, err := Parse(tt.id)
-			if tt.wantError {
-				require.Error(t, err)
-				return
-			}
-			require.NoError(t, err)
-			require.Equal(t, tt.result, id)
-			require.Equal(t, tt.id, id.String(), "проверяем корректность работы метода String, полученное ID должно совпадать с исходным")
-		})
-	}
-}
-
-func Test_Map(t *testing.T) {
-	tests := []struct {
-		name string
-		id   *ID
-	}{
-		{
-			name: "ServiceID",
-			id:   &ID{Descriptor: &ServiceID{ServiceID: "<service_id>"}},
-		},
-		{
-			name: "UserID",
-			id:   &ID{Descriptor: &UserID{UserID: "<user_id>"}},
-		},
-		{
-			name: "OrganizationID",
-			id:   &ID{Descriptor: &OrganizationID{OrganizationID: "<org_id>"}},
-		},
-		{
-			name: "SpaceID",
-			id:   &ID{Descriptor: &SpaceID{SpaceID: "<space_id>"}},
-		},
-		{
-			name: "ClientID",
-			id: &ID{Descriptor: &ClientID{
-				SpaceID:  SpaceID{SpaceID: "<space_id>"},
-				ClientID: "<client_id>",
-			}},
-		},
-		{
-			name: "RoleID",
-			id: &ID{Descriptor: &RoleID{
-				SpaceID: SpaceID{SpaceID: "<space_id>"},
-				RoleID:  "<role_id>",
-			}},
-		},
-		{
-			name: "EnvironmentID",
-			id: &ID{Descriptor: &EnvironmentID{
-				SpaceID:       SpaceID{SpaceID: "<space_id>"},
-				EnvironmentID: "<env_id>",
-			}},
-		},
-		{
-			name: "CollectionID",
-			id: &ID{Descriptor: &CollectionID{
-				EnvironmentID: EnvironmentID{
-					SpaceID:       SpaceID{SpaceID: "<space_id>"},
-					EnvironmentID: "<env_id>",
-				},
-				CollectionID: "<collection_id>",
-			}},
-		},
-		{
-			name: "Schema ID",
-			id: &ID{Descriptor: &SchemaID{
-				EnvironmentID: EnvironmentID{
-					SpaceID:       SpaceID{SpaceID: "<space_id>"},
-					EnvironmentID: "<env_id>",
-				},
-				CollectionID: "<collection_id>",
-			}},
-		},
-		{
-			name: "ItemID",
-			id: &ID{Descriptor: &ItemID{
-				CollectionID: CollectionID{
-					EnvironmentID: EnvironmentID{
-						SpaceID:       SpaceID{SpaceID: "<space_id>"},
-						EnvironmentID: "<env_id>",
-					},
-					CollectionID: "<collection_id>",
-				},
-				ItemID: "<item_id>",
-			}},
-		},
-		{
-			name: "RevisionID",
-			id: &ID{Descriptor: &RevisionID{
-				ItemID: ItemID{
-					CollectionID: CollectionID{
-						EnvironmentID: EnvironmentID{
-							SpaceID:       SpaceID{SpaceID: "<space_id>"},
-							EnvironmentID: "<env_id>",
-						},
-						CollectionID: "<collection_id>",
-					},
-					ItemID: "<item_id>",
-				},
-				RevisionID: "<rev_id>",
-			}},
-		},
-		{
-			name: "FieldID",
-			id: &ID{Descriptor: &FieldID{
-				ItemID: ItemID{
-					CollectionID: CollectionID{
-						EnvironmentID: EnvironmentID{
-							SpaceID:       SpaceID{SpaceID: "<space_id>"},
-							EnvironmentID: "<env_id>",
-						},
-						CollectionID: "<collection_id>",
-					},
-					ItemID: "<item_id>",
-				},
-				FieldName: "<field_name>",
-			}},
-		},
-		{
-			name: "SystemID",
-			id:   &ID{Descriptor: &SystemID{}},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			v, err := FromMap(tt.id.ToMap())
-			require.NoError(t, err)
-			assert.Equal(t, tt.id, v, "проверка FromMap для типа ID, должен быть равен исходному значению")
-			assert.Equal(t, v.ToMap(), tt.id.ToMap())
-		})
-	}
-}
diff --git a/id/item.go b/id/item.go
index 70c3e7be..01410c4b 100644
--- a/id/item.go
+++ b/id/item.go
@@ -1,61 +1,60 @@
 package id
 
+import "fmt"
+
 const (
 	Item        = "item"
 	ItemsPrefix = "items"
 )
 
-type ItemID struct {
-	CollectionID
+type ItemId struct {
+	CollectionId
 	ItemID string `json:"item_id,omitempty" bson:"item_id,omitempty"`
 }
 
-func (t *ItemID) Type() string { return Item }
+func (i *ItemId) New() Descriptor {
+	return &ItemId{}
+}
+
+func (i *ItemId) Type() string { return Item }
 
-func (t *ItemID) String() string {
-	return Join(t.CollectionID.String(), ItemsPrefix, t.ItemID)
+func (i *ItemId) String() string {
+	return Join(i.CollectionId.String(), ItemsPrefix, i.ItemID)
 
 }
 
-func (t *ItemID) ToMap() map[string]any {
-	m := t.CollectionID.ToMap()
-	m["item_id"] = t.ItemID
+func (i *ItemId) Map() map[string]any {
+	m := i.CollectionId.Map()
+	m["item_id"] = i.ItemID
 	m["type"] = Item
 	return m
 }
 
-func (t *ItemID) FromMap(m map[string]any) error {
-	if err := t.CollectionID.FromMap(m); err != nil {
-		return err
+func (i *ItemId) FromParts(parts []string) error {
+	if len(parts) != 8 || parts[6] != ItemsPrefix {
+		return ErrInvalid
 	}
-	t.ItemID = m["item_id"].(string)
-	return nil
-}
 
-func (t *ItemID) Validate() error {
-	if t.ItemID == "" {
-		return ErrInvalidID
+	if err := i.CollectionId.FromParts(parts[:6]); err != nil {
+		return err
 	}
 
-	return t.CollectionID.Validate()
+	i.ItemID = parts[7]
+	return nil
 }
 
-func parseItemID(parts []string) (*ItemID, error) {
-	if len(parts) != 8 || parts[6] != ItemsPrefix {
-		return nil, ErrInvalidID
-	}
-
-	collID, err := parseCollectionID(parts[:6])
-	if err != nil {
-		return nil, err
+func (i *ItemId) FromMap(m map[string]any) error {
+	i.ItemID = m["item_id"].(string)
+	if i.ItemID == "" {
+		return fmt.Errorf("%w: ItemId required", ErrInvalid)
 	}
 
-	var id ItemID
-	id.CollectionID = *collID
-	id.ItemID = parts[7]
-	return &id, nil
+	return i.CollectionId.FromMap(m)
 }
 
-func NewItemID(spaceID, envID, collID, id string) *ID {
-	return &ID{Descriptor: &ItemID{CollectionID: CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: id}}
+func (i *ItemId) Validate() error {
+	if i.ItemID == "" {
+		return fmt.Errorf("%w: ItemId required", ErrInvalid)
+	}
+	return i.CollectionId.Validate()
 }
diff --git a/id/json.go b/id/json.go
deleted file mode 100644
index 34b87589..00000000
--- a/id/json.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package id
-
-import (
-	jsoniter "github.com/json-iterator/go"
-)
-
-func (id *ID) MarshalJSON() ([]byte, error) {
-	return jsoniter.Marshal(id.String())
-}
-
-func (id *ID) UnmarshalJSON(b []byte) error {
-	var s string
-	var err error
-	if err = jsoniter.Unmarshal(b, &s); err != nil {
-		return err
-	}
-	t, err := Parse(s)
-	if err != nil {
-		return err
-	}
-	*id = *t
-	return nil
-}
diff --git a/id/object_id.go b/id/object_id.go
new file mode 100644
index 00000000..e84ea1c0
--- /dev/null
+++ b/id/object_id.go
@@ -0,0 +1,102 @@
+package id
+
+import (
+	jsoniter "github.com/json-iterator/go"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+	"strings"
+)
+
+const Separator = '/'
+
+type Descriptor interface {
+	New() Descriptor
+	String() string
+	Type() string
+	Map() map[string]any
+	Validate() error
+	FromMap(map[string]any) error
+	FromParts([]string) error
+}
+
+type ObjectId struct {
+	Descriptor
+}
+
+func NewObjectId(in any) (*ObjectId, error) {
+	switch v := in.(type) {
+	case Descriptor:
+		return &ObjectId{Descriptor: v}, nil
+	case string:
+		return FromString(v)
+	case map[string]any:
+		return FromMap(v)
+	}
+	return FromObject(in)
+}
+
+func MustObjectId(in any) *ObjectId {
+	oid, err := NewObjectId(in)
+	if err != nil {
+		panic(err)
+	}
+	return oid
+}
+
+func (oid *ObjectId) MarshalJSON() ([]byte, error) {
+	return jsoniter.Marshal(oid.String())
+}
+
+func (oid *ObjectId) UnmarshalJSON(b []byte) error {
+	var s string
+	var err error
+	if err = jsoniter.Unmarshal(b, &s); err != nil {
+		return err
+	}
+	t, err := FromString(s)
+	if err != nil {
+		return err
+	}
+	*oid = *t
+	return nil
+}
+
+func (oid *ObjectId) MarshalBSONValue() (bsontype.Type, []byte, error) {
+	return bson.MarshalValue(oid.String())
+}
+
+func (oid *ObjectId) UnmarshalBSONValue(btype bsontype.Type, data []byte) error {
+	if btype != bson.TypeString {
+		return ErrInvalidType
+	}
+	dec, err := bson.NewDecoder(bsonrw.NewBSONValueReader(btype, data))
+	if err != nil {
+		return err
+	}
+	var str string
+	if err = dec.Decode(&str); err != nil {
+		return err
+	}
+	t, err := FromString(str)
+	if err != nil {
+		return err
+	}
+	*oid = *t
+	return nil
+}
+
+func Join(parts ...string) string {
+	s := strings.Join(parts, string(Separator))
+	if s[0] != Separator {
+		s = string(Separator) + s
+	}
+	return s
+}
+
+func Split(id string) []string {
+	if id[0] != Separator {
+		return nil
+	}
+	return strings.Split(id[1:], string(Separator))
+}
diff --git a/id/object_id_test.go b/id/object_id_test.go
new file mode 100644
index 00000000..b6714573
--- /dev/null
+++ b/id/object_id_test.go
@@ -0,0 +1,306 @@
+package id
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func Test_ParseID(t *testing.T) {
+	tests := []struct {
+		name      string
+		id        string
+		result    *ObjectId
+		wantError bool
+	}{
+		{
+			name:   "SpaceId",
+			id:     "/spaces/<space_id>",
+			result: MustObjectId("/spaces/<space_id>"),
+		},
+	}
+	//{
+	//	name:   "ServiceID",
+	//	id:     "/services/<service_id>",
+	//	result: &ID{Descriptor: &ServiceID{ServiceID: "<service_id>"}},
+	//},
+	//{
+	//	name:   "UserID",
+	//	id:     "/users/<user_id>",
+	//	result: &ID{Descriptor: &UserID{UserID: "<user_id>"}},
+	//},
+	//{
+	//	name:   "OrganizationID",
+	//	id:     "/orgs/<org_id>",
+	//	result: &ID{Descriptor: &OrganizationID{OrganizationID: "<org_id>"}},
+	//},
+	//	{
+	//		name:   "SpaceId",
+	//		id:     "/spaces/<space_id>",
+	//		result: &ID{Descriptor: &SpaceID{SpaceID: "<space_id>"}},
+	//	},
+	//	{
+	//		name: "ClientID",
+	//		id:   "/spaces/<space_id>/clients/<client_id>",
+	//		result: &ID{Descriptor: &ClientID{
+	//			SpaceID:  SpaceID{SpaceID: "<space_id>"},
+	//			ClientID: "<client_id>",
+	//		}},
+	//	},
+	//	{
+	//		name: "RoleID",
+	//		id:   "/spaces/<space_id>/roles/<role_id>",
+	//		result: &ID{Descriptor: &RoleID{
+	//			SpaceID: SpaceID{SpaceID: "<space_id>"},
+	//			RoleID:  "<role_id>",
+	//		}},
+	//	},
+	//	{
+	//		name: "EnvironmentID",
+	//		id:   "/spaces/<space_id>/envs/<env_id>",
+	//		result: &ID{Descriptor: &EnvironmentID{
+	//			SpaceID:       SpaceID{SpaceID: "<space_id>"},
+	//			EnvironmentID: "<env_id>",
+	//		}},
+	//	},
+	//	{
+	//		name: "CollectionId",
+	//		id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+	//		result: &ID{Descriptor: &CollectionId{
+	//			EnvironmentID: EnvironmentID{
+	//				SpaceID:       SpaceID{SpaceID: "<space_id>"},
+	//				EnvironmentID: "<env_id>",
+	//			},
+	//			CollectionId: "<collection_id>",
+	//		}},
+	//	},
+	//	{
+	//		name: "SchemaID",
+	//		id:   "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>",
+	//		result: &ID{Descriptor: &SchemaID{
+	//			EnvironmentID: EnvironmentID{
+	//				SpaceID:       SpaceID{SpaceID: "<space_id>"},
+	//				EnvironmentID: "<env_id>",
+	//			},
+	//			CollectionId: "<collection_id>",
+	//		}},
+	//	},
+	//	{
+	//		name: "ItemId",
+	//		id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+	//		result: &ID{Descriptor: &ItemId{
+	//			CollectionId: CollectionId{
+	//				EnvironmentID: EnvironmentID{
+	//					SpaceID:       SpaceID{SpaceID: "<space_id>"},
+	//					EnvironmentID: "<env_id>",
+	//				},
+	//				CollectionId: "<collection_id>",
+	//			},
+	//			ItemId: "<item_id>",
+	//		}},
+	//	},
+	//	{
+	//		name: "RevisionID",
+	//		id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>",
+	//		result: &ID{Descriptor: &RevisionID{
+	//			ItemId: ItemId{
+	//				CollectionId: CollectionId{
+	//					EnvironmentID: EnvironmentID{
+	//						SpaceID:       SpaceID{SpaceID: "<space_id>"},
+	//						EnvironmentID: "<env_id>",
+	//					},
+	//					CollectionId: "<collection_id>",
+	//				},
+	//				ItemId: "<item_id>",
+	//			},
+	//			RevisionID: "<rev_id>",
+	//		}},
+	//	},
+	//	{
+	//		name: "FieldId",
+	//		id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field_name>",
+	//		result: &ID{Descriptor: &FieldId{
+	//			ItemId: ItemId{
+	//				CollectionId: CollectionId{
+	//					EnvironmentID: EnvironmentID{
+	//						SpaceID:       SpaceID{SpaceID: "<space_id>"},
+	//						EnvironmentID: "<env_id>",
+	//					},
+	//					CollectionId: "<collection_id>",
+	//				},
+	//				ItemId: "<item_id>",
+	//			},
+	//			FieldName: "<field_name>",
+	//		}},
+	//	},
+	//	{
+	//		name:   "SystemID",
+	//		id:     "/system",
+	//		result: &ID{Descriptor: &SystemID{}},
+	//	},
+	//	{
+	//		name:      "With error #1: no backslash in the beginning of id",
+	//		id:        "spaces/<space_id>",
+	//		result:    nil,
+	//		wantError: true,
+	//	},
+	//	{
+	//		name:      "With error #2: backslash in the end of id",
+	//		id:        "/spaces/<space_id>/",
+	//		result:    nil,
+	//		wantError: true,
+	//	},
+	//	{
+	//		name:      "With error #3: typo in 'spaces'",
+	//		id:        "/space/<space_id>",
+	//		result:    nil,
+	//		wantError: true,
+	//	},
+	//	{
+	//		name:      "With error #4: no space_id in id",
+	//		id:        "/spaces",
+	//		result:    nil,
+	//		wantError: true,
+	//	},
+	//	{
+	//		name:      "With error #5: multiple backslashes in the end of id",
+	//		id:        "/spaces/<space_id>///",
+	//		result:    nil,
+	//		wantError: true,
+	//	},
+	//}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			id, err := NewObjectId(tt.id)
+			if tt.wantError {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+			require.Equal(t, tt.result, id)
+			require.Equal(t, tt.id, id.String())
+		})
+	}
+}
+
+//func Test_Map(t *testing.T) {
+//	tests := []struct {
+//		name string
+//		id   *ID
+//	}{
+//		{
+//			name: "ServiceID",
+//			id:   &ID{Descriptor: &ServiceID{ServiceID: "<service_id>"}},
+//		},
+//		{
+//			name: "UserID",
+//			id:   &ID{Descriptor: &UserID{UserID: "<user_id>"}},
+//		},
+//		{
+//			name: "OrganizationID",
+//			id:   &ID{Descriptor: &OrganizationID{OrganizationID: "<org_id>"}},
+//		},
+//		{
+//			name: "SpaceId",
+//			id:   &ID{Descriptor: &SpaceID{SpaceID: "<space_id>"}},
+//		},
+//		{
+//			name: "ClientID",
+//			id: &ID{Descriptor: &ClientID{
+//				SpaceID:  SpaceID{SpaceID: "<space_id>"},
+//				ClientID: "<client_id>",
+//			}},
+//		},
+//		{
+//			name: "RoleID",
+//			id: &ID{Descriptor: &RoleID{
+//				SpaceID: SpaceID{SpaceID: "<space_id>"},
+//				RoleID:  "<role_id>",
+//			}},
+//		},
+//		{
+//			name: "EnvironmentID",
+//			id: &ID{Descriptor: &EnvironmentID{
+//				SpaceID:       SpaceID{SpaceID: "<space_id>"},
+//				EnvironmentID: "<env_id>",
+//			}},
+//		},
+//		{
+//			name: "CollectionId",
+//			id: &ID{Descriptor: &CollectionId{
+//				EnvironmentId: EnvironmentID{
+//					SpaceID:       SpaceID{SpaceID: "<space_id>"},
+//					EnvironmentID: "<env_id>",
+//				},
+//				CollectionID: "<collection_id>",
+//			}},
+//		},
+//		{
+//			name: "Schema ID",
+//			id: &ID{Descriptor: &SchemaID{
+//				EnvironmentID: EnvironmentID{
+//					SpaceID:       SpaceID{SpaceID: "<space_id>"},
+//					EnvironmentID: "<env_id>",
+//				},
+//				CollectionId: "<collection_id>",
+//			}},
+//		},
+//		{
+//			name: "ItemId",
+//			id: &ID{Descriptor: &ItemId{
+//				CollectionId: CollectionId{
+//					EnvironmentId: EnvironmentID{
+//						SpaceID:       SpaceID{SpaceID: "<space_id>"},
+//						EnvironmentID: "<env_id>",
+//					},
+//					CollectionID: "<collection_id>",
+//				},
+//				ItemID: "<item_id>",
+//			}},
+//		},
+//		{
+//			name: "RevisionID",
+//			id: &ID{Descriptor: &RevisionID{
+//				ItemId: ItemId{
+//					CollectionId: CollectionId{
+//						EnvironmentId: EnvironmentID{
+//							SpaceID:       SpaceID{SpaceID: "<space_id>"},
+//							EnvironmentID: "<env_id>",
+//						},
+//						CollectionID: "<collection_id>",
+//					},
+//					ItemID: "<item_id>",
+//				},
+//				RevisionID: "<rev_id>",
+//			}},
+//		},
+//		{
+//			name: "FieldId",
+//			id: &ID{Descriptor: &FieldId{
+//				ItemId: ItemId{
+//					CollectionId: CollectionId{
+//						EnvironmentId: EnvironmentID{
+//							SpaceID:       SpaceID{SpaceID: "<space_id>"},
+//							EnvironmentID: "<env_id>",
+//						},
+//						CollectionID: "<collection_id>",
+//					},
+//					ItemID: "<item_id>",
+//				},
+//				FieldName: "<field_name>",
+//			}},
+//		},
+//		{
+//			name: "SystemID",
+//			id:   &ID{Descriptor: &SystemID{}},
+//		},
+//	}
+//	for _, tt := range tests {
+//		t.Run(tt.name, func(t *testing.T) {
+//			v, err := FromMap(tt.id.Map())
+//			require.NoError(t, err)
+//			assert.Equal(t, tt.id, v, "проверка FromMap для типа ID, должен быть равен исходному значению")
+//			assert.Equal(t, v.Map(), tt.id.Map())
+//		})
+//	}
+//}
diff --git a/id/client.go b/id/old/client.go
similarity index 93%
rename from id/client.go
rename to id/old/client.go
index db941c5a..a329ad67 100644
--- a/id/client.go
+++ b/id/old/client.go
@@ -1,4 +1,4 @@
-package id
+package old
 
 const (
 	Client        = "client"
@@ -17,8 +17,8 @@ func (t *ClientID) String() string {
 
 }
 
-func (t *ClientID) ToMap() map[string]any {
-	m := t.SpaceID.ToMap()
+func (t *ClientID) Map() map[string]any {
+	m := t.SpaceID.Map()
 	m["client_id"] = t.ClientID
 	m["type"] = Client
 	return m
diff --git a/id/organization.go b/id/old/organization.go
similarity index 94%
rename from id/organization.go
rename to id/old/organization.go
index fe9d2283..2790d859 100644
--- a/id/organization.go
+++ b/id/old/organization.go
@@ -1,4 +1,4 @@
-package id
+package old
 
 const (
 	Organization        = "organization"
@@ -15,7 +15,7 @@ func (t *OrganizationID) String() string {
 	return Join(OrganizationsPrefix, t.OrganizationID)
 }
 
-func (t *OrganizationID) ToMap() map[string]any {
+func (t *OrganizationID) Map() map[string]any {
 	return map[string]any{
 		"organization_id": t.OrganizationID,
 		"type":            Organization,
diff --git a/id/revision.go b/id/old/revision.go
similarity index 94%
rename from id/revision.go
rename to id/old/revision.go
index 0cb417e1..143eef64 100644
--- a/id/revision.go
+++ b/id/old/revision.go
@@ -1,4 +1,4 @@
-package id
+package old
 
 const (
 	Revision        = "revision"
@@ -17,8 +17,8 @@ func (t *RevisionID) String() string {
 
 }
 
-func (t *RevisionID) ToMap() map[string]any {
-	m := t.ItemID.ToMap()
+func (t *RevisionID) Map() map[string]any {
+	m := t.ItemID.Map()
 	m["rev_id"] = t.RevisionID
 	m["type"] = Revision
 	return m
diff --git a/id/role.go b/id/old/role.go
similarity index 93%
rename from id/role.go
rename to id/old/role.go
index abb6537f..6e5d10c9 100644
--- a/id/role.go
+++ b/id/old/role.go
@@ -1,4 +1,4 @@
-package id
+package old
 
 const (
 	Role        = "role"
@@ -17,8 +17,8 @@ func (t *RoleID) String() string {
 
 }
 
-func (t *RoleID) ToMap() map[string]any {
-	m := t.SpaceID.ToMap()
+func (t *RoleID) Map() map[string]any {
+	m := t.SpaceID.Map()
 	m["role_id"] = t.RoleID
 	m["type"] = Role
 	return m
diff --git a/id/schema.go b/id/old/schema.go
similarity index 93%
rename from id/schema.go
rename to id/old/schema.go
index e3afee8c..6d02e1c1 100644
--- a/id/schema.go
+++ b/id/old/schema.go
@@ -1,4 +1,4 @@
-package id
+package old
 
 const (
 	Schema       = "schema"
@@ -16,8 +16,8 @@ func (t *SchemaID) String() string {
 	return Join(t.EnvironmentID.String(), SchemaPrefix, t.CollectionID)
 }
 
-func (t *SchemaID) ToMap() map[string]any {
-	m := t.EnvironmentID.ToMap()
+func (t *SchemaID) Map() map[string]any {
+	m := t.EnvironmentID.Map()
 	m["col_id"] = t.CollectionID
 	m["type"] = Schema
 	return m
diff --git a/id/service.go b/id/old/service.go
similarity index 94%
rename from id/service.go
rename to id/old/service.go
index 23bb23aa..5281a974 100644
--- a/id/service.go
+++ b/id/old/service.go
@@ -1,4 +1,4 @@
-package id
+package old
 
 const (
 	Service        = "service"
@@ -15,7 +15,7 @@ func (t *ServiceID) String() string {
 	return Join(ServicesPrefix, t.ServiceID)
 }
 
-func (t *ServiceID) ToMap() map[string]any {
+func (t *ServiceID) Map() map[string]any {
 	return map[string]any{
 		"service_id": t.ServiceID,
 		"type":       Service,
diff --git a/id/system.go b/id/old/system.go
similarity index 88%
rename from id/system.go
rename to id/old/system.go
index de2f3c25..90d15622 100644
--- a/id/system.go
+++ b/id/old/system.go
@@ -1,4 +1,4 @@
-package id
+package old
 
 const System = "system"
 
@@ -6,7 +6,7 @@ type SystemID struct{}
 
 func (t *SystemID) Type() string                   { return Space }
 func (t *SystemID) String() string                 { return string(Separator) + System }
-func (t *SystemID) ToMap() map[string]any          { return map[string]any{"type": System} }
+func (t *SystemID) Map() map[string]any            { return map[string]any{"type": System} }
 func (t *SystemID) FromMap(m map[string]any) error { return nil }
 func (t *SystemID) Validate() error                { return nil }
 
diff --git a/id/user.go b/id/old/user.go
similarity index 93%
rename from id/user.go
rename to id/old/user.go
index c76f6c9f..b4b41147 100644
--- a/id/user.go
+++ b/id/old/user.go
@@ -1,4 +1,4 @@
-package id
+package old
 
 const (
 	User        = "user"
@@ -15,7 +15,7 @@ func (t *UserID) String() string {
 	return Join(UsersPrefix, t.UserID)
 }
 
-func (t *UserID) ToMap() map[string]any {
+func (t *UserID) Map() map[string]any {
 	return map[string]any{
 		"user_id": t.UserID,
 		"type":    User,
diff --git a/id/registry.go b/id/registry.go
new file mode 100644
index 00000000..2a55da00
--- /dev/null
+++ b/id/registry.go
@@ -0,0 +1,108 @@
+package id
+
+import (
+	"fmt"
+	"reflect"
+)
+
+var registry = NewRegistry()
+
+type ObjectHandler func(interface{}) *ObjectId
+
+type Registry struct {
+	handlers    map[reflect.Type]ObjectHandler
+	descriptors map[string]Descriptor
+}
+
+func NewRegistry() *Registry {
+	return &Registry{
+		handlers:    make(map[reflect.Type]ObjectHandler),
+		descriptors: make(map[string]Descriptor),
+	}
+}
+
+// Register method adds a handler based on its type.
+func (r *Registry) RegisterObjectHandler(t reflect.Type, h ObjectHandler) {
+	r.handlers[t] = h
+}
+
+func (r *Registry) RegisterDescriptor(d Descriptor) {
+	r.descriptors[d.Type()] = d
+}
+
+func (r *Registry) FromParts(s string) (*ObjectId, error) {
+	parts := Split(s)
+
+	for _, d := range r.descriptors {
+		id := d.New()
+		if err := id.FromParts(parts); err == nil {
+			return &ObjectId{Descriptor: id}, nil
+		}
+	}
+
+	return nil, fmt.Errorf("%w: %s", ErrInvalid, s)
+}
+
+func (r *Registry) FromMap(m map[string]interface{}) (*ObjectId, error) {
+	t, ok := m["type"].(string)
+	if !ok {
+		return nil, ErrTypeNotFound
+	}
+
+	if d, ok := r.descriptors[t]; ok {
+		id := d.New()
+		if err := id.FromMap(m); err != nil {
+			return nil, err
+		}
+		return &ObjectId{Descriptor: id}, nil
+	}
+
+	return nil, fmt.Errorf("%w: %s", t, ErrInvalid)
+}
+
+func (r *Registry) FromObject(v interface{}) (*ObjectId, error) {
+	t := reflect.TypeOf(v)
+	if handler, ok := r.handlers[t]; ok {
+		i := handler(v)
+		if i == nil {
+			panic(fmt.Sprintf("handler for %s returned nil", t))
+		}
+		return i, nil
+	}
+	return nil, fmt.Errorf("%s : %w", t, ErrHandlerNotFound)
+}
+
+func FromMap(m map[string]interface{}) (*ObjectId, error) {
+	return registry.FromMap(m)
+}
+
+func FromString(s string) (*ObjectId, error) {
+	return registry.FromParts(s)
+}
+
+type ObjectIdentifier interface {
+	ObjectId() *ObjectId
+}
+
+func FromObject(v interface{}) (*ObjectId, error) {
+	if id, ok := v.(ObjectIdentifier); ok {
+		return id.ObjectId(), nil
+	}
+	return registry.FromObject(v)
+}
+
+func RegisterSystemIds(r *Registry) {
+	r.RegisterDescriptor(&SpaceId{})
+	r.RegisterDescriptor(&EnvironmentId{})
+	r.RegisterDescriptor(&CollectionId{})
+	r.RegisterDescriptor(&ItemId{})
+	r.RegisterDescriptor(&FieldId{})
+}
+
+func GetRegistry() *Registry {
+	return registry
+}
+
+func init() {
+	RegisterSystemIds(registry)
+}
diff --git a/id/space.go b/id/space.go
index 39096673..2cd8d143 100644
--- a/id/space.go
+++ b/id/space.go
@@ -1,48 +1,57 @@
 package id
 
+import (
+	"fmt"
+)
+
 const (
 	Space        = "space"
 	SpacesPrefix = "spaces"
 )
 
-type SpaceID struct {
+var _ Descriptor = &SpaceId{}
+
+type SpaceId struct {
 	SpaceID string `json:"space_id,omitempty" bson:"space_id,omitempty"`
 }
 
-func (t *SpaceID) Type() string { return Space }
+func (id *SpaceId) New() Descriptor {
+	return &SpaceId{}
+}
+
+func (id *SpaceId) Type() string { return Space }
 
-func (t *SpaceID) String() string {
-	return Join(SpacesPrefix, t.SpaceID)
+func (id *SpaceId) String() string {
+	return Join(SpacesPrefix, id.SpaceID)
 }
 
-func (t *SpaceID) ToMap() map[string]any {
-	return map[string]any{
-		"space_id": t.SpaceID,
-		"type":     Space,
+func (id *SpaceId) FromParts(parts []string) error {
+	if len(parts) != 2 || parts[0] != SpacesPrefix {
+		return ErrInvalid
 	}
-}
 
-func (t *SpaceID) FromMap(m map[string]any) error {
-	t.SpaceID = m["space_id"].(string)
+	id.SpaceID = parts[1]
 	return nil
 }
 
-func (t *SpaceID) Validate() error {
-	if t.SpaceID == "" {
-		return ErrInvalidID
+func (id *SpaceId) Map() map[string]any {
+	return map[string]any{
+		"space_id": id.SpaceID,
+		"type":     id.Type(),
 	}
-	return nil
 }
 
-func parseSpaceID(parts []string) (*SpaceID, error) {
-	var id SpaceID
-	if len(parts) != 2 || parts[0] != SpacesPrefix {
-		return nil, ErrInvalidID
+func (id *SpaceId) FromMap(m map[string]any) error {
+	id.SpaceID, _ = m["space_id"].(string)
+	if id.SpaceID == "" {
+		return fmt.Errorf("%w: SpaceId required", ErrInvalid)
 	}
-
-	id.SpaceID = parts[1]
-	return &id, nil
+	return nil
 }
-func NewSpaceID(id string) *ID {
-	return &ID{Descriptor: &SpaceID{SpaceID: id}}
+
+func (id *SpaceId) Validate() error {
+	if id.SpaceID == "" {
+		return fmt.Errorf("%w: SpaceId required", ErrInvalid)
+	}
+	return nil
 }
diff --git a/id/system/system.go b/id/system/system.go
new file mode 100644
index 00000000..6bd72608
--- /dev/null
+++ b/id/system/system.go
@@ -0,0 +1,68 @@
+package system
+
+import (
+	"git.perx.ru/perxis/perxis-go/id"
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"git.perx.ru/perxis/perxis-go/pkg/spaces"
+	"git.perx.ru/perxis/perxis-go/pkg/users"
+	"reflect"
+)
+
+func Handler(obj any) *id.ObjectId {
+	switch val := obj.(type) {
+	case *spaces.Space:
+		var i id.SpaceId
+		i.SpaceID = val.ID
+		return id.MustObjectId(&i)
+	case *environments.Environment:
+		var i id.EnvironmentId
+		i.SpaceID = val.SpaceID
+		i.EnvironmentID = val.ID
+		return id.MustObjectId(&i)
+	case *collections.Collection:
+		var i id.CollectionId
+		i.SpaceID = val.SpaceID
+		i.EnvironmentID = val.EnvID
+		i.CollectionID = val.ID
+		return id.MustObjectId(&i)
+	case *items.Item:
+		var i id.ItemId
+		i.SpaceID = val.SpaceID
+		i.EnvironmentID = val.EnvID
+		i.CollectionID = val.CollectionID
+		i.ItemID = val.ID
+		return id.MustObjectId(&i)
+	case *users.User:
+		// TODO
+		break
+	case *auth.UserPrincipal:
+		// TODO: Вернуть UserID
+		break
+	case *auth.ClientPrincipal:
+		// TODO
+		break
+	case *auth.SystemPrincipal:
+		// TODO
+		break
+	case *auth.Anonymous:
+		// TODO
+		break
+	}
+	return nil
+}
+
+// Register registers object handler for system types into the provided Registry.
+func Register(r *id.Registry) {
+	r.RegisterObjectHandler(reflect.TypeOf(&spaces.Space{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&environments.Environment{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&collections.Collection{}), Handler)
+	r.RegisterObjectHandler(reflect.TypeOf(&items.Item{}), Handler)
+	// TODO: Добавить регистрацию для всех системных типов
+}
+
+func init() {
+	Register(id.GetRegistry())
+}
diff --git a/id/test/object_id_test.go b/id/test/object_id_test.go
new file mode 100644
index 00000000..437b3bb2
--- /dev/null
+++ b/id/test/object_id_test.go
@@ -0,0 +1,354 @@
+package test
+
+import (
+	"git.perx.ru/perxis/perxis-go/id"
+	_ "git.perx.ru/perxis/perxis-go/id/system"
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	"git.perx.ru/perxis/perxis-go/pkg/spaces"
+	"github.com/stretchr/testify/require"
+	"testing"
+)
+
+func Test_SpaceId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name:   "valid string",
+			in:     "/spaces/<space_id>",
+			result: &id.ObjectId{Descriptor: &id.SpaceId{SpaceID: "<space_id>"}},
+		},
+		{
+			name:   "valid object",
+			in:     &spaces.Space{ID: "<space_id>"},
+			out:    "/spaces/<space_id>",
+			result: &id.ObjectId{Descriptor: &id.SpaceId{SpaceID: "<space_id>"}},
+		},
+		{
+			name:   "valid map",
+			in:     map[string]any{"type": "space", "space_id": "<space_id>"},
+			out:    "/spaces/<space_id>",
+			result: &id.ObjectId{Descriptor: &id.SpaceId{SpaceID: "<space_id>"}},
+		},
+		{
+			name:   "invalid map",
+			in:     map[string]any{"type": "space"},
+			out:    "/spaces/<space_id>",
+			result: &id.ObjectId{Descriptor: &id.SpaceId{SpaceID: "<space_id>"}},
+			err:    id.ErrInvalid,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_EnvironmentId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "invalid string",
+			in:   "/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+			err: id.ErrInvalid,
+		},
+		{
+			name: "valid object",
+			in:   &environments.Environment{SpaceID: "<space_id>", ID: "<env_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "environment", "space_id": "<space_id>", "env_id": "<env_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "invalid map 1",
+			in:   map[string]any{"type": "environment", "space_id": "<space_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+			err: id.ErrInvalid,
+		},
+		{
+			name: "invalid map 2",
+			in:   map[string]any{"type": "environment", "env_id": "<env_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>",
+			result: &id.ObjectId{Descriptor: &id.EnvironmentId{
+				SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+			err: id.ErrInvalid,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_CollectionId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.CollectionId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+		{
+			name: "valid object",
+			in:   &collections.Collection{SpaceID: "<space_id>", EnvID: "<env_id>", ID: "<collection_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.CollectionId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "collection", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+			result: &id.ObjectId{Descriptor: &id.CollectionId{
+				EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"},
+				CollectionID:  "<collection_id>",
+			}},
+		},
+		// TODO: Add test cases.
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_ItemId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+			result: &id.ObjectId{Descriptor: &id.ItemId{
+				CollectionId: id.CollectionId{
+					EnvironmentId: id.EnvironmentId{
+						SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+		{
+			name: "valid object",
+			in:   &items.Item{SpaceID: "<space_id>", EnvID: "<env_id>", CollectionID: "<collection_id>", ID: "<item_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+			result: &id.ObjectId{Descriptor: &id.ItemId{
+				CollectionId: id.CollectionId{
+					EnvironmentId: id.EnvironmentId{
+						SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "item", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>", "item_id": "<item_id>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+			result: &id.ObjectId{Descriptor: &id.ItemId{
+				CollectionId: id.CollectionId{
+					EnvironmentId: id.EnvironmentId{
+						SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+		// TODO: Add test cases.
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
+
+func Test_FieldId(t *testing.T) {
+
+	tests := []struct {
+		name   string
+		in     any
+		out    string
+		result *id.ObjectId
+		err    error
+	}{
+		{
+			name: "valid string",
+			in:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field>",
+			result: &id.ObjectId{Descriptor: &id.FieldId{
+				ItemId: id.ItemId{
+					CollectionId: id.CollectionId{
+						EnvironmentId: id.EnvironmentId{
+							SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				Field: "<field>",
+			}},
+		},
+		{
+			name: "valid map",
+			in:   map[string]any{"type": "field", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>", "item_id": "<item_id>", "field": "<field>"},
+			out:  "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field>",
+			result: &id.ObjectId{Descriptor: &id.FieldId{
+				ItemId: id.ItemId{
+					CollectionId: id.CollectionId{
+						EnvironmentId: id.EnvironmentId{
+							SpaceId:       id.SpaceId{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				Field: "<field>",
+			}},
+		},
+		// TODO: Add test cases.
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			i, err := id.NewObjectId(tt.in)
+
+			if tt.err != nil {
+				require.ErrorIs(t, err, tt.err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, tt.result, i)
+			if tt.out == "" {
+				require.Equal(t, tt.in, i.String())
+			} else {
+				require.Equal(t, tt.out, i.String())
+			}
+		})
+	}
+}
-- 
GitLab