diff --git a/id/bson.go b/id/bson.go new file mode 100644 index 0000000000000000000000000000000000000000..9d44c5a8074361b381524dd7c99ad77906cfc68c --- /dev/null +++ b/id/bson.go @@ -0,0 +1,32 @@ +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/bson_test.go b/id/bson_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c8080b5d7ae12181343a8f6502e76484c414e578 --- /dev/null +++ b/id/bson_test.go @@ -0,0 +1,107 @@ +package id + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" +) + +func TestID_MarshalUnmarshalBSON(t *testing.T) { + tests := []struct { + name string + id *ID + }{ + { + name: "OrganizationID", + id: &ID{Descriptor: &OrganizationID{OrganizationID: "1"}}, + }, + { + name: "UserID", + id: &ID{Descriptor: &UserID{UserID: "1"}}, + }, + { + name: "ServiceID", + id: &ID{Descriptor: &ServiceID{ServiceID: "1"}}, + }, + { + name: "SpaceID", + id: &ID{Descriptor: &SpaceID{SpaceID: "1"}}, + }, + { + name: "EnvironmentID", + id: &ID{Descriptor: &EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}, + }, + { + name: "ClientID", + id: &ID{Descriptor: &ClientID{ClientID: "1", SpaceID: SpaceID{SpaceID: "1"}}}, + }, + { + name: "RoleID", + 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: "SchemaID", + 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: "RevisionID", + 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: "SystemID", + id: &ID{Descriptor: &SystemID{}}, + }, + } + type test struct { + Text string + ID *ID + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &test{Text: tt.name, ID: tt.id} + + b, err := bson.Marshal(s) + require.NoError(t, err) + + var v *test + require.NoError(t, bson.Unmarshal(b, &v)) + assert.Equal(t, s, v, "после Unmarshal объект должен быть идентичен исходному") + }) + } +} + +func TestID_ExampleBSON(t *testing.T) { + type data struct { + ID *ID + Text string + Number int + } + + test := &data{ + ID: &ID{Descriptor: &SpaceID{SpaceID: Space}}, + Text: "text", + Number: 1, + } + + b, err := bson.Marshal(test) + require.NoError(t, err) + + buf := new(data) + err = bson.Unmarshal(b, &buf) + require.NoError(t, err) + assert.Equal(t, test, buf, "после Unmarshal объект должен совпадать с исходным") +} diff --git a/id/client.go b/id/client.go new file mode 100644 index 0000000000000000000000000000000000000000..db941c5ad54b539551193004674ac15f15d314be --- /dev/null +++ b/id/client.go @@ -0,0 +1,61 @@ +package id + +const ( + Client = "client" + ClientsPrefix = "clients" +) + +type ClientID struct { + SpaceID + ClientID string `json:"client_id,omitempty" bson:"client_id,omitempty"` +} + +func (t *ClientID) Type() string { return Client } + +func (t *ClientID) String() string { + return Join(t.SpaceID.String(), ClientsPrefix, t.ClientID) + +} + +func (t *ClientID) ToMap() map[string]any { + m := t.SpaceID.ToMap() + m["client_id"] = t.ClientID + m["type"] = Client + return m +} + +func (t *ClientID) FromMap(m map[string]any) error { + if err := t.SpaceID.FromMap(m); err != nil { + return err + } + t.ClientID = m["client_id"].(string) + return nil +} + +func (t *ClientID) Validate() error { + if t.ClientID == "" { + return ErrInvalidID + } + + return t.SpaceID.Validate() +} + +func parseClientID(parts []string) (*ClientID, error) { + if len(parts) != 4 || parts[2] != ClientsPrefix { + return nil, ErrInvalidID + } + + spaceID, err := parseSpaceID(parts[:2]) + if err != nil { + return nil, err + } + + var id ClientID + id.SpaceID = *spaceID + id.ClientID = parts[3] + return &id, nil +} + +func NewClientID(spaceID, id string) *ID { + return &ID{Descriptor: &ClientID{SpaceID: SpaceID{SpaceID: spaceID}, ClientID: id}} +} diff --git a/id/collection.go b/id/collection.go new file mode 100644 index 0000000000000000000000000000000000000000..e0f37585b41e77b58530cc50f249283beb6d8eca --- /dev/null +++ b/id/collection.go @@ -0,0 +1,60 @@ +package id + +const ( + Collection = "collection" + CollectionsPrefix = "cols" +) + +type CollectionID struct { + EnvironmentID + CollectionID string `json:"col_id,omitempty" bson:"col_id, omitempty"` +} + +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() + 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 { + return err + } + t.CollectionID = m["col_id"].(string) + return nil +} + +func (t *CollectionID) Validate() error { + if t.CollectionID == "" { + return ErrInvalidID + } + + return t.EnvironmentID.Validate() +} + +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 + } + + 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}} +} diff --git a/id/environment.go b/id/environment.go new file mode 100644 index 0000000000000000000000000000000000000000..d42df3e658bc04ae70989d199257a7836bd30f00 --- /dev/null +++ b/id/environment.go @@ -0,0 +1,61 @@ +package id + +const ( + Environment = "environment" + EnvironmentsPrefix = "envs" +) + +type EnvironmentID struct { + SpaceID + EnvironmentID string `json:"env_id,omitempty" bson:"env_id,omitempty"` +} + +func (t *EnvironmentID) Type() string { return Environment } + +func (t *EnvironmentID) String() string { + return Join(t.SpaceID.String(), EnvironmentsPrefix, t.EnvironmentID) + +} + +func (t *EnvironmentID) ToMap() map[string]any { + m := t.SpaceID.ToMap() + m["env_id"] = t.EnvironmentID + m["type"] = Environment + return m +} + +func (t *EnvironmentID) FromMap(m map[string]any) error { + if err := t.SpaceID.FromMap(m); err != nil { + return err + } + t.EnvironmentID = m["env_id"].(string) + return nil +} + +func (t *EnvironmentID) Validate() error { + if t.EnvironmentID == "" { + return ErrInvalidID + } + + return t.SpaceID.Validate() +} + +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 + } + + 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}} +} diff --git a/id/field.go b/id/field.go new file mode 100644 index 0000000000000000000000000000000000000000..ca1577552dc7df38d555549ddfc53a51e70e63f1 --- /dev/null +++ b/id/field.go @@ -0,0 +1,60 @@ +package id + +const ( + Field = "field" + FieldsPrefix = "fields" +) + +type FieldID struct { + ItemID + FieldName string `json:"field_name,omitempty" bson:"field_name,omitempty"` +} + +func (t *FieldID) Type() string { return Field } + +func (t *FieldID) String() string { + return Join(t.ItemID.String(), FieldsPrefix, t.FieldName) + +} + +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 { + return err + } + t.FieldName = m["field_name"].(string) + return nil +} + +func (t *FieldID) Validate() error { + if t.FieldName == "" { + return ErrInvalidID + } + + return t.ItemID.Validate() +} + +func parseFieldID(parts []string) (*FieldID, error) { + if len(parts) != 10 || parts[8] != FieldsPrefix { + return nil, ErrInvalidID + } + + itemID, err := parseItemID(parts[:8]) + if err != nil { + return nil, err + } + + var id FieldID + id.ItemID = *itemID + id.FieldName = parts[9] + return &id, 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}} +} diff --git a/id/id.go b/id/id.go new file mode 100644 index 0000000000000000000000000000000000000000..ca75c29f94b6fe9100b435e8f546835991bc9fa3 --- /dev/null +++ b/id/id.go @@ -0,0 +1,143 @@ +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 new file mode 100644 index 0000000000000000000000000000000000000000..041f1cdc6018e6c84c0c4c6a4be4b3944ef34082 --- /dev/null +++ b/id/id_test.go @@ -0,0 +1,301 @@ +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 new file mode 100644 index 0000000000000000000000000000000000000000..70c3e7be95b517a85e07e487eb00f94f9f73e14f --- /dev/null +++ b/id/item.go @@ -0,0 +1,61 @@ +package id + +const ( + Item = "item" + ItemsPrefix = "items" +) + +type ItemID struct { + CollectionID + ItemID string `json:"item_id,omitempty" bson:"item_id,omitempty"` +} + +func (t *ItemID) Type() string { return Item } + +func (t *ItemID) String() string { + return Join(t.CollectionID.String(), ItemsPrefix, t.ItemID) + +} + +func (t *ItemID) ToMap() map[string]any { + m := t.CollectionID.ToMap() + m["item_id"] = t.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 + } + t.ItemID = m["item_id"].(string) + return nil +} + +func (t *ItemID) Validate() error { + if t.ItemID == "" { + return ErrInvalidID + } + + return t.CollectionID.Validate() +} + +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 + } + + var id ItemID + id.CollectionID = *collID + id.ItemID = parts[7] + return &id, nil +} + +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}} +} diff --git a/id/json.go b/id/json.go new file mode 100644 index 0000000000000000000000000000000000000000..34b87589dfd81e6b793c5812fb2b90f43fb724f1 --- /dev/null +++ b/id/json.go @@ -0,0 +1,23 @@ +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/json_test.go b/id/json_test.go new file mode 100644 index 0000000000000000000000000000000000000000..afe831d4ef6038eaa6bc8a565b0623334cdf6bc8 --- /dev/null +++ b/id/json_test.go @@ -0,0 +1,101 @@ +package id + +import ( + "testing" + + jsoniter "github.com/json-iterator/go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestID_MarshalUnmarshalJSON(t *testing.T) { + tests := []struct { + name string + id *ID + }{ + { + name: "OrganizationID", + id: &ID{Descriptor: &OrganizationID{OrganizationID: "1"}}, + }, + { + name: "UserID", + id: &ID{Descriptor: &UserID{UserID: "1"}}, + }, + { + name: "ServiceID", + id: &ID{Descriptor: &ServiceID{ServiceID: "1"}}, + }, + { + name: "SpaceID", + id: &ID{Descriptor: &SpaceID{SpaceID: "1"}}, + }, + { + name: "EnvironmentID", + id: &ID{Descriptor: &EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}, + }, + { + name: "ClientID", + id: &ID{Descriptor: &ClientID{ClientID: "1", SpaceID: SpaceID{SpaceID: "1"}}}, + }, + { + name: "RoleID", + 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: "SchemaID", + 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: "RevisionID", + 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: "SystemID", + id: &ID{Descriptor: &SystemID{}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b, err := jsoniter.Marshal(&tt.id) + require.NoError(t, err) + + var i ID + require.NoError(t, jsoniter.Unmarshal(b, &i)) + assert.Equal(t, tt.id, &i, "после Unmarshal объект должен быть идентичен исходному") + }) + } +} + +func TestID_ExampleJSON(t *testing.T) { + type data struct { + ID *ID + Text string + Number int + } + + test := &data{ + ID: &ID{Descriptor: &SpaceID{SpaceID: Space}}, + Text: "text", + Number: 1, + } + + b, err := jsoniter.Marshal(test) + require.NoError(t, err) + + buf := new(data) + err = jsoniter.Unmarshal(b, &buf) + require.NoError(t, err) + assert.Equal(t, test, buf, "после Unmarshal объект должен совпадать с исходным") +} diff --git a/id/organization.go b/id/organization.go new file mode 100644 index 0000000000000000000000000000000000000000..fe9d22837977b486e78689ebee5f426bdee80fcf --- /dev/null +++ b/id/organization.go @@ -0,0 +1,49 @@ +package id + +const ( + Organization = "organization" + OrganizationsPrefix = "orgs" +) + +type OrganizationID struct { + OrganizationID string `json:"organization_id,omitempty" bson:"organization_id,omitempty"` +} + +func (t *OrganizationID) Type() string { return Organization } + +func (t *OrganizationID) String() string { + return Join(OrganizationsPrefix, t.OrganizationID) +} + +func (t *OrganizationID) ToMap() map[string]any { + return map[string]any{ + "organization_id": t.OrganizationID, + "type": Organization, + } +} + +func (t *OrganizationID) FromMap(m map[string]any) error { + t.OrganizationID = m["organization_id"].(string) + return nil +} + +func (t *OrganizationID) Validate() error { + if t.OrganizationID == "" { + return ErrInvalidID + } + return nil +} + +func parseOrganizationID(parts []string) (*OrganizationID, error) { + var id OrganizationID + if len(parts) != 2 || parts[0] != OrganizationsPrefix { + return nil, ErrInvalidID + } + + id.OrganizationID = parts[1] + return &id, nil +} + +func NewOrganizationID(id string) *ID { + return &ID{Descriptor: &OrganizationID{OrganizationID: id}} +} diff --git a/id/revision.go b/id/revision.go new file mode 100644 index 0000000000000000000000000000000000000000..0cb417e132fe1683f1e53a081e7c6244820478df --- /dev/null +++ b/id/revision.go @@ -0,0 +1,61 @@ +package id + +const ( + Revision = "revision" + RevisionsPrefix = "revs" +) + +type RevisionID struct { + ItemID + RevisionID string `json:"rev_id" bson:"rev_id,omitempty"` +} + +func (t *RevisionID) Type() string { return Revision } + +func (t *RevisionID) String() string { + return Join(t.ItemID.String(), RevisionsPrefix, t.RevisionID) + +} + +func (t *RevisionID) ToMap() map[string]any { + m := t.ItemID.ToMap() + m["rev_id"] = t.RevisionID + m["type"] = Revision + return m +} + +func (t *RevisionID) FromMap(m map[string]any) error { + if err := t.ItemID.FromMap(m); err != nil { + return err + } + t.RevisionID = m["rev_id"].(string) + return nil +} + +func (t *RevisionID) Validate() error { + if t.RevisionID == "" { + return ErrInvalidID + } + + return t.ItemID.Validate() +} + +func parseRevisionID(parts []string) (*RevisionID, error) { + if len(parts) != 10 || parts[8] != RevisionsPrefix { + return nil, ErrInvalidID + } + + itemID, err := parseItemID(parts[:8]) + if err != nil { + return nil, err + } + + var id RevisionID + id.ItemID = *itemID + id.RevisionID = parts[9] + return &id, nil +} + +func NewRevisionID(spaceID, envID, collID, itemID, id string) *ID { + return &ID{Descriptor: &RevisionID{ItemID: ItemID{CollectionID: CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: itemID}, RevisionID: id}} +} diff --git a/id/role.go b/id/role.go new file mode 100644 index 0000000000000000000000000000000000000000..abb6537fc605d3f01f644a67a724cc81e3a6b54b --- /dev/null +++ b/id/role.go @@ -0,0 +1,61 @@ +package id + +const ( + Role = "role" + RolesPrefix = "roles" +) + +type RoleID struct { + SpaceID + RoleID string `json:"role_id,omitempty" bson:"role_id,omitempty"` +} + +func (t *RoleID) Type() string { return Role } + +func (t *RoleID) String() string { + return Join(t.SpaceID.String(), RolesPrefix, t.RoleID) + +} + +func (t *RoleID) ToMap() map[string]any { + m := t.SpaceID.ToMap() + m["role_id"] = t.RoleID + m["type"] = Role + return m +} + +func (t *RoleID) FromMap(m map[string]any) error { + if err := t.SpaceID.FromMap(m); err != nil { + return err + } + t.RoleID = m["role_id"].(string) + return nil +} + +func (t *RoleID) Validate() error { + if t.RoleID == "" { + return ErrInvalidID + } + + return t.SpaceID.Validate() +} + +func parseRoleID(parts []string) (*RoleID, error) { + if len(parts) != 4 || parts[2] != RolesPrefix { + return nil, ErrInvalidID + } + + spaceID, err := parseSpaceID(parts[:2]) + if err != nil { + return nil, err + } + + var id RoleID + id.SpaceID = *spaceID + id.RoleID = parts[3] + return &id, nil +} + +func NewRoleID(spaceID, id string) *ID { + return &ID{Descriptor: &RoleID{SpaceID: SpaceID{SpaceID: spaceID}, RoleID: id}} +} diff --git a/id/schema.go b/id/schema.go new file mode 100644 index 0000000000000000000000000000000000000000..e3afee8c6cf29bca6cae1f1eedd72253c47362a5 --- /dev/null +++ b/id/schema.go @@ -0,0 +1,60 @@ +package id + +const ( + Schema = "schema" + SchemaPrefix = "schema" +) + +type SchemaID struct { + EnvironmentID + CollectionID string `json:"col_id" bson:"col_id,omitempty"` +} + +func (t *SchemaID) Type() string { return Schema } + +func (t *SchemaID) String() string { + return Join(t.EnvironmentID.String(), SchemaPrefix, t.CollectionID) +} + +func (t *SchemaID) ToMap() map[string]any { + m := t.EnvironmentID.ToMap() + m["col_id"] = t.CollectionID + m["type"] = Schema + return m +} + +func (t *SchemaID) FromMap(m map[string]any) error { + if err := t.EnvironmentID.FromMap(m); err != nil { + return err + } + t.CollectionID = m["col_id"].(string) + return nil +} + +func (t *SchemaID) Validate() error { + if t.CollectionID == "" { + return ErrInvalidID + } + + return t.EnvironmentID.Validate() +} + +func parseSchemaID(parts []string) (*SchemaID, error) { + if len(parts) != 6 || parts[4] != SchemaPrefix { + return nil, ErrInvalidID + } + + envID, err := parseEnvironmentID(parts[:4]) + if err != nil { + return nil, err + } + + var id SchemaID + id.EnvironmentID = *envID + id.CollectionID = parts[5] + return &id, nil +} + +func NewSchemaID(spaceID, envID, id string) *ID { + return &ID{Descriptor: &SchemaID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: id}} +} diff --git a/id/service.go b/id/service.go new file mode 100644 index 0000000000000000000000000000000000000000..23bb23aa500fbba082e1aae578eae6da5624a5b9 --- /dev/null +++ b/id/service.go @@ -0,0 +1,49 @@ +package id + +const ( + Service = "service" + ServicesPrefix = "services" +) + +type ServiceID struct { + ServiceID string `json:"service_id,omitempty" bson:"service_id,omitempty"` +} + +func (t *ServiceID) Type() string { return Service } + +func (t *ServiceID) String() string { + return Join(ServicesPrefix, t.ServiceID) +} + +func (t *ServiceID) ToMap() map[string]any { + return map[string]any{ + "service_id": t.ServiceID, + "type": Service, + } +} + +func (t *ServiceID) FromMap(m map[string]any) error { + t.ServiceID = m["service_id"].(string) + return nil +} + +func (t *ServiceID) Validate() error { + if t.ServiceID == "" { + return ErrInvalidID + } + return nil +} + +func parseServiceID(parts []string) (*ServiceID, error) { + var id ServiceID + if len(parts) != 2 || parts[0] != ServicesPrefix { + return nil, ErrInvalidID + } + + id.ServiceID = parts[1] + return &id, nil +} + +func NewServiceID(id string) *ID { + return &ID{Descriptor: &ServiceID{ServiceID: id}} +} diff --git a/id/space.go b/id/space.go new file mode 100644 index 0000000000000000000000000000000000000000..39096673456d74462ef203b10814396fe48b2bcf --- /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) ToMap() 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/system.go b/id/system.go new file mode 100644 index 0000000000000000000000000000000000000000..de2f3c2571896e448f8aad17be32df58b73a6ea4 --- /dev/null +++ b/id/system.go @@ -0,0 +1,22 @@ +package id + +const System = "system" + +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) FromMap(m map[string]any) error { return nil } +func (t *SystemID) Validate() error { return nil } + +func parseSystemID(parts []string) (*SystemID, error) { + var id SystemID + if len(parts) != 1 || parts[0] != System { + return nil, ErrInvalidID + } + return &id, nil +} +func NewSystemID() *ID { + return &ID{Descriptor: &SystemID{}} +} diff --git a/id/user.go b/id/user.go new file mode 100644 index 0000000000000000000000000000000000000000..c76f6c9fa0e8ce440a1ef15e36bbb82a73301be5 --- /dev/null +++ b/id/user.go @@ -0,0 +1,49 @@ +package id + +const ( + User = "user" + UsersPrefix = "users" +) + +type UserID struct { + UserID string `json:"user_id,omitempty" bson:"user_id,omitempty"` +} + +func (t *UserID) Type() string { return User } + +func (t *UserID) String() string { + return Join(UsersPrefix, t.UserID) +} + +func (t *UserID) ToMap() map[string]any { + return map[string]any{ + "user_id": t.UserID, + "type": User, + } +} + +func (t *UserID) FromMap(m map[string]any) error { + t.UserID = m["user_id"].(string) + return nil +} + +func (t *UserID) Validate() error { + if t.UserID == "" { + return ErrInvalidID + } + return nil +} + +func parseUserID(parts []string) (*UserID, error) { + var id UserID + if len(parts) != 2 || parts[0] != UsersPrefix { + return nil, ErrInvalidID + } + + id.UserID = parts[1] + return &id, nil +} + +func NewUserID(id string) *ID { + return &ID{Descriptor: &UserID{UserID: id}} +} diff --git a/perxis-proto b/perxis-proto index ecd756865a06583ce52bff6265c0fd5e159d93b9..63410745d6008eaa9d9b00626b5f5b6891ac9189 160000 --- a/perxis-proto +++ b/perxis-proto @@ -1 +1 @@ -Subproject commit ecd756865a06583ce52bff6265c0fd5e159d93b9 +Subproject commit 63410745d6008eaa9d9b00626b5f5b6891ac9189