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