diff --git a/id/bson.go b/id/bson.go deleted file mode 100644 index 9d44c5a8074361b381524dd7c99ad77906cfc68c..0000000000000000000000000000000000000000 --- 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/bson_test.go b/id/bson_test.go index c8080b5d7ae12181343a8f6502e76484c414e578..7ae62ccba4e1dbc2e74ac48b8c5890216a6cba50 100644 --- a/id/bson_test.go +++ b/id/bson_test.go @@ -11,64 +11,64 @@ import ( func TestID_MarshalUnmarshalBSON(t *testing.T) { tests := []struct { name string - id *ID + id *ObjectId }{ { name: "OrganizationID", - id: &ID{Descriptor: &OrganizationID{OrganizationID: "1"}}, + id: &ObjectId{Descriptor: &OrganizationId{OrganizationID: "1"}}, }, { name: "UserID", - id: &ID{Descriptor: &UserID{UserID: "1"}}, + id: &ObjectId{Descriptor: &UserId{UserID: "1"}}, }, { name: "ServiceID", - id: &ID{Descriptor: &ServiceID{ServiceID: "1"}}, + id: &ObjectId{Descriptor: &ServiceId{ServiceID: "1"}}, }, { - name: "SpaceID", - id: &ID{Descriptor: &SpaceID{SpaceID: "1"}}, + name: "SpaceId", + id: &ObjectId{Descriptor: &SpaceId{SpaceID: "1"}}, }, { name: "EnvironmentID", - id: &ID{Descriptor: &EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}, + id: &ObjectId{Descriptor: &EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}, }, { name: "ClientID", - id: &ID{Descriptor: &ClientID{ClientID: "1", SpaceID: SpaceID{SpaceID: "1"}}}, + id: &ObjectId{Descriptor: &ClientId{ClientID: "1", SpaceId: SpaceId{SpaceID: "1"}}}, }, { name: "RoleID", - id: &ID{Descriptor: &RoleID{RoleID: "1", SpaceID: SpaceID{SpaceID: "1"}}}, + id: &ObjectId{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: &ObjectId{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: &ObjectId{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: &ObjectId{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: &ObjectId{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: &ObjectId{Descriptor: &FieldId{Field: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}}}, }, { name: "SystemID", - id: &ID{Descriptor: &SystemID{}}, + id: &ObjectId{Descriptor: &SystemId{}}, }, } type test struct { Text string - ID *ID + ID *ObjectId } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -86,13 +86,14 @@ func TestID_MarshalUnmarshalBSON(t *testing.T) { func TestID_ExampleBSON(t *testing.T) { type data struct { - ID *ID + ID *ObjectId + Text string Number int } test := &data{ - ID: &ID{Descriptor: &SpaceID{SpaceID: Space}}, + ID: &ObjectId{Descriptor: &SpaceId{SpaceID: Space}}, Text: "text", Number: 1, } diff --git a/id/client.go b/id/client.go index db941c5ad54b539551193004674ac15f15d314be..704f8d5f27f756f1f65d73ab138f08cc090bdf7d 100644 --- a/id/client.go +++ b/id/client.go @@ -1,61 +1,67 @@ package id +import ( + "fmt" + + "git.perx.ru/perxis/perxis-go/pkg/clients" +) + const ( Client = "client" ClientsPrefix = "clients" ) -type ClientID struct { - SpaceID +var _ Descriptor = &ClientId{} + +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 (id *ClientId) New() Descriptor { + return &ClientId{} } -func (t *ClientID) ToMap() map[string]any { - m := t.SpaceID.ToMap() - m["client_id"] = t.ClientID - m["type"] = Client - return m +func (id *ClientId) Type() string { return Client } + +func (id *ClientId) String() string { + return Join(id.SpaceId.String(), ClientsPrefix, id.ClientID) } -func (t *ClientID) FromMap(m map[string]any) error { - if err := t.SpaceID.FromMap(m); err != nil { +func (id *ClientId) FromParts(parts []string) error { + if len(parts) != 4 || parts[2] != ClientsPrefix { + return ErrInvalidID + } + if err := id.SpaceId.FromParts(parts[:2]); err != nil { return err } - t.ClientID = m["client_id"].(string) + id.ClientID = parts[3] return nil } -func (t *ClientID) Validate() error { - if t.ClientID == "" { - return ErrInvalidID - } - - return t.SpaceID.Validate() +func (id *ClientId) Map() map[string]any { + m := id.SpaceId.Map() + m["client_id"] = id.ClientID + m["type"] = Client + return m } -func parseClientID(parts []string) (*ClientID, error) { - if len(parts) != 4 || parts[2] != ClientsPrefix { - return nil, ErrInvalidID +func (id *ClientId) FromMap(m map[string]any) error { + id.ClientID, _ = m["client_id"].(string) + if id.ClientID == "" { + return fmt.Errorf("%w: ClientID required", ErrInvalidID) } + return id.SpaceId.FromMap(m) +} - spaceID, err := parseSpaceID(parts[:2]) - if err != nil { - return nil, err +func (id *ClientId) Validate() error { + if id.ClientID == "" { + return fmt.Errorf("%w: ClientID required", ErrInvalidID) } - var id ClientID - id.SpaceID = *spaceID - id.ClientID = parts[3] - return &id, nil + return id.SpaceId.Validate() } -func NewClientID(spaceID, id string) *ID { - return &ID{Descriptor: &ClientID{SpaceID: SpaceID{SpaceID: spaceID}, ClientID: id}} +func NewClientId(c clients.Client) *ObjectId { + return &ObjectId{Descriptor: &ClientId{SpaceId: SpaceId{SpaceID: c.SpaceID}, ClientID: c.ID}} } diff --git a/id/collection.go b/id/collection.go index e0f37585b41e77b58530cc50f249283beb6d8eca..6518513b59238f0377aafe495dd4cac1f43a8436 100644 --- a/id/collection.go +++ b/id/collection.go @@ -1,60 +1,74 @@ package id +import ( + "fmt" + + "git.perx.ru/perxis/perxis-go/pkg/collections" +) + 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{EnvironmentId: EnvironmentId{SpaceId: SpaceId{}}} +} -func (t *CollectionID) String() string { - return Join(t.EnvironmentID.String(), CollectionsPrefix, t.CollectionID) +func (id *CollectionId) Type() string { return Collection } + +func (id *CollectionId) String() string { + return Join(id.EnvironmentId.String(), CollectionsPrefix, id.CollectionID) } -func (t *CollectionID) ToMap() map[string]any { - m := t.EnvironmentID.ToMap() - m["col_id"] = t.CollectionID +func (id *CollectionId) Map() map[string]any { + m := id.EnvironmentId.Map() + m["col_id"] = id.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 ErrInvalidID + } + 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", ErrInvalidID) } - - 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 (id *CollectionId) Validate() error { + if id.CollectionID == "" { + return fmt.Errorf("%w: CollectionID required", ErrInvalidID) } - - var id CollectionID - id.CollectionID = parts[5] - id.EnvironmentID = *envID - return &id, nil + return id.EnvironmentId.Validate() } -func NewCollectionID(spaceID, envID, id string) *ID { - return &ID{Descriptor: &CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: id}} +func NewCollectionId(coll collections.Collection) *ObjectId { + return &ObjectId{Descriptor: &CollectionId{ + EnvironmentId: EnvironmentId{ + SpaceId: SpaceId{ + SpaceID: coll.SpaceID, + }, + EnvironmentID: coll.EnvID, + }, + CollectionID: coll.ID, + }} } diff --git a/id/environment.go b/id/environment.go index d42df3e658bc04ae70989d199257a7836bd30f00..0da72195ae0e1bc9d9262e6b958f61c30c0a2f36 100644 --- a/id/environment.go +++ b/id/environment.go @@ -1,61 +1,71 @@ package id +import ( + "fmt" + + "git.perx.ru/perxis/perxis-go/pkg/environments" +) + const ( Environment = "environment" EnvironmentsPrefix = "envs" ) -type EnvironmentID struct { - SpaceID +var _ Descriptor = &EnvironmentId{} + +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 (t *EnvironmentID) String() string { - return Join(t.SpaceID.String(), EnvironmentsPrefix, t.EnvironmentID) +func (id *EnvironmentId) Type() string { return "environment" } + +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 ErrInvalidID + } + 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", ErrInvalidID) } - - 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 +func (id *EnvironmentId) Validate() error { + if id.EnvironmentID == "" { + return fmt.Errorf("%w: EnvironmetnID required", 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 + return id.SpaceId.Validate() } - -func NewEnvironmentID(spaceID, id string) *ID { - return &ID{Descriptor: &EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: id}} +func NewEnvironmentId(env environments.Environment) *ObjectId { + return &ObjectId{Descriptor: &EnvironmentId{ + SpaceId: SpaceId{ + SpaceID: env.SpaceID, + }, + EnvironmentID: env.ID, + }} } diff --git a/id/error.go b/id/error.go new file mode 100644 index 0000000000000000000000000000000000000000..e8332dc96a651e337cdc34ec83d06442ff27048e --- /dev/null +++ b/id/error.go @@ -0,0 +1,11 @@ +package id + +import "errors" + +var ( + ErrInvalidID = errors.New("invalid ObjectId") + ErrUnknownType = 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 ca1577552dc7df38d555549ddfc53a51e70e63f1..024732f2d44d7a2ac4db63418ba113563a379c8d 100644 --- a/id/field.go +++ b/id/field.go @@ -1,60 +1,82 @@ 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 (t *FieldID) String() string { - return Join(t.ItemID.String(), FieldsPrefix, t.FieldName) +var _ Descriptor = &FieldId{} +func (id *FieldId) New() Descriptor { + return &FieldId{} } -func (t *FieldID) ToMap() map[string]any { - m := t.ItemID.ToMap() - m["field_name"] = t.FieldName - m["type"] = Field - return m +func (id *FieldId) Type() string { return Field } + +func (id *FieldId) String() string { + return Join(id.ItemId.String(), FieldsPrefix, id.Field) + } -func (t *FieldID) FromMap(m map[string]any) error { - if err := t.ItemID.FromMap(m); err != nil { +func (id *FieldId) FromParts(parts []string) error { + if len(parts) != 10 || parts[8] != FieldsPrefix { + return ErrInvalidID + } + if err := id.ItemId.FromParts(parts[:8]); err != nil { return err } - t.FieldName = m["field_name"].(string) + id.Field = parts[9] return nil } -func (t *FieldID) Validate() error { - if t.FieldName == "" { - return ErrInvalidID - } - - return t.ItemID.Validate() +func (id *FieldId) Map() map[string]any { + m := id.ItemId.Map() + m["field"] = id.Field + m["type"] = Field + return m } -func parseFieldID(parts []string) (*FieldID, error) { - if len(parts) != 10 || parts[8] != FieldsPrefix { - return nil, ErrInvalidID +func (id *FieldId) FromMap(m map[string]any) error { + id.Field = m["field"].(string) + if id.Field == "" { + return fmt.Errorf("%w: Field required", ErrInvalidID) + } + + if err := id.ItemId.FromMap(m); err != nil { + return err } + return nil +} - itemID, err := parseItemID(parts[:8]) - if err != nil { - return nil, err +func (id *FieldId) Validate() error { + if id.Field == "" { + return fmt.Errorf("%w: Field required", ErrInvalidID) } - var id FieldID - id.ItemID = *itemID - id.FieldName = parts[9] - return &id, nil + return id.ItemId.Validate() } -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 NewFieldId(spaceID, envID, collID, itemID, id string) *ObjectId { + return &ObjectId{Descriptor: &FieldId{ + ItemId: ItemId{ + CollectionId: CollectionId{ + EnvironmentId: EnvironmentId{ + SpaceId: SpaceId{ + SpaceID: spaceID, + }, + EnvironmentID: envID, + }, + CollectionID: collID, + }, + ItemID: itemID, + }, + Field: id, + }} } diff --git a/id/id.go b/id/id.go deleted file mode 100644 index ca75c29f94b6fe9100b435e8f546835991bc9fa3..0000000000000000000000000000000000000000 --- 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 041f1cdc6018e6c84c0c4c6a4be4b3944ef34082..0000000000000000000000000000000000000000 --- 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 70c3e7be95b517a85e07e487eb00f94f9f73e14f..dbfb8e18cb5128fc0ab26a4298203a38a4af1ad6 100644 --- a/id/item.go +++ b/id/item.go @@ -1,61 +1,80 @@ package id +import ( + "fmt" + + "git.perx.ru/perxis/perxis-go/pkg/items" +) + 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 } +var _ Descriptor = &ItemId{} + +func (i *ItemId) New() Descriptor { + return &ItemId{} +} -func (t *ItemID) String() string { - return Join(t.CollectionID.String(), ItemsPrefix, t.ItemID) +func (i *ItemId) Type() string { return Item } + +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 { +func (i *ItemId) FromParts(parts []string) error { + if len(parts) != 8 || parts[6] != ItemsPrefix { + return ErrInvalidID + } + + if err := i.CollectionId.FromParts(parts[:6]); err != nil { return err } - t.ItemID = m["item_id"].(string) + + i.ItemID = parts[7] return nil } -func (t *ItemID) Validate() error { - if t.ItemID == "" { - return ErrInvalidID +func (i *ItemId) FromMap(m map[string]any) error { + i.ItemID = m["item_id"].(string) + if i.ItemID == "" { + return fmt.Errorf("%w: ItemId required", ErrInvalidID) } - return t.CollectionID.Validate() + return i.CollectionId.FromMap(m) } -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) Validate() error { + if i.ItemID == "" { + return fmt.Errorf("%w: ItemId required", ErrInvalidID) } - - var id ItemID - id.CollectionID = *collID - id.ItemID = parts[7] - return &id, nil + return i.CollectionId.Validate() } - -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 NewItemId(i items.Item) *ObjectId { + return &ObjectId{Descriptor: &ItemId{ + CollectionId: CollectionId{ + EnvironmentId: EnvironmentId{ + SpaceId: SpaceId{ + SpaceID: i.SpaceID, + }, + EnvironmentID: i.EnvID, + }, + CollectionID: i.CollectionID, + }, + ItemID: i.ID, + }} } diff --git a/id/json.go b/id/json.go deleted file mode 100644 index 34b87589dfd81e6b793c5812fb2b90f43fb724f1..0000000000000000000000000000000000000000 --- 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/json_test.go b/id/json_test.go index afe831d4ef6038eaa6bc8a565b0623334cdf6bc8..0da4f292db94ffc93558b76ba702f63b0ce5a121 100644 --- a/id/json_test.go +++ b/id/json_test.go @@ -11,59 +11,59 @@ import ( func TestID_MarshalUnmarshalJSON(t *testing.T) { tests := []struct { name string - id *ID + id *ObjectId }{ { name: "OrganizationID", - id: &ID{Descriptor: &OrganizationID{OrganizationID: "1"}}, + id: &ObjectId{Descriptor: &OrganizationId{OrganizationID: "1"}}, }, { name: "UserID", - id: &ID{Descriptor: &UserID{UserID: "1"}}, + id: &ObjectId{Descriptor: &UserId{UserID: "1"}}, }, { name: "ServiceID", - id: &ID{Descriptor: &ServiceID{ServiceID: "1"}}, + id: &ObjectId{Descriptor: &ServiceId{ServiceID: "1"}}, }, { - name: "SpaceID", - id: &ID{Descriptor: &SpaceID{SpaceID: "1"}}, + name: "SpaceId", + id: &ObjectId{Descriptor: &SpaceId{SpaceID: "1"}}, }, { name: "EnvironmentID", - id: &ID{Descriptor: &EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}, + id: &ObjectId{Descriptor: &EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}, }, { name: "ClientID", - id: &ID{Descriptor: &ClientID{ClientID: "1", SpaceID: SpaceID{SpaceID: "1"}}}, + id: &ObjectId{Descriptor: &ClientId{ClientID: "1", SpaceId: SpaceId{SpaceID: "1"}}}, }, { name: "RoleID", - id: &ID{Descriptor: &RoleID{RoleID: "1", SpaceID: SpaceID{SpaceID: "1"}}}, + id: &ObjectId{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: &ObjectId{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: &ObjectId{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: &ObjectId{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: &ObjectId{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: &ObjectId{Descriptor: &FieldId{Field: "1", ItemId: ItemId{ItemID: "1", CollectionId: CollectionId{CollectionID: "1", EnvironmentId: EnvironmentId{EnvironmentID: "1", SpaceId: SpaceId{SpaceID: "1"}}}}}}, }, { name: "SystemID", - id: &ID{Descriptor: &SystemID{}}, + id: &ObjectId{Descriptor: &SystemId{}}, }, } for _, tt := range tests { @@ -71,7 +71,7 @@ func TestID_MarshalUnmarshalJSON(t *testing.T) { b, err := jsoniter.Marshal(&tt.id) require.NoError(t, err) - var i ID + var i ObjectId require.NoError(t, jsoniter.Unmarshal(b, &i)) assert.Equal(t, tt.id, &i, "после Unmarshal объект должен быть идентичен исходному") }) @@ -80,13 +80,13 @@ func TestID_MarshalUnmarshalJSON(t *testing.T) { func TestID_ExampleJSON(t *testing.T) { type data struct { - ID *ID + ID *ObjectId Text string Number int } test := &data{ - ID: &ID{Descriptor: &SpaceID{SpaceID: Space}}, + ID: &ObjectId{Descriptor: &SpaceId{SpaceID: Space}}, Text: "text", Number: 1, } diff --git a/id/object_id.go b/id/object_id.go new file mode 100644 index 0000000000000000000000000000000000000000..e84ea1c0fff9960bfef7584fbd68699509cfa2a6 --- /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 0000000000000000000000000000000000000000..572f0da96c190a2138875c6b7dcb24c237fbe6fa --- /dev/null +++ b/id/object_id_test.go @@ -0,0 +1,242 @@ +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 *ObjectId + wantError bool + }{ + { + name: "SpaceId", + id: "/spaces/<space_id>", + result: MustObjectId("/spaces/<space_id>"), + }, + { + name: "ServiceID", + id: "/services/<service_id>", + result: MustObjectId("/services/<service_id>"), + }, + { + name: "UserID", + id: "/users/<user_id>", + result: MustObjectId("/users/<user_id>"), + }, + { + name: "OrganizationID", + id: "/orgs/<org_id>", + result: MustObjectId("/orgs/<org_id>"), + }, + { + name: "ClientID", + id: "/spaces/<space_id>/clients/<client_id>", + result: MustObjectId("/spaces/<space_id>/clients/<client_id>"), + }, + { + name: "RoleID", + id: "/spaces/<space_id>/roles/<role_id>", + result: MustObjectId("/spaces/<space_id>/roles/<role_id>"), + }, + { + name: "EnvironmentID", + id: "/spaces/<space_id>/envs/<env_id>", + result: MustObjectId("/spaces/<space_id>/envs/<env_id>"), + }, + { + name: "CollectionId", + id: "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>", + result: MustObjectId("/spaces/<space_id>/envs/<env_id>/cols/<collection_id>"), + }, + { + name: "SchemaID", + id: "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>", + result: MustObjectId("/spaces/<space_id>/envs/<env_id>/schema/<collection_id>"), + }, + { + name: "ItemId", + id: "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>", + result: MustObjectId("/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>"), + }, + { + name: "RevisionID", + id: "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>", + result: MustObjectId("/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>"), + }, + { + name: "FieldId", + id: "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field_name>", + result: MustObjectId("/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field_name>"), + }, + { + 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 *ObjectId + }{ + { + name: "ServiceID", + id: &ObjectId{Descriptor: &ServiceId{ServiceID: "<service_id>"}}, + }, + { + name: "UserID", + id: &ObjectId{Descriptor: &UserId{UserID: "<user_id>"}}, + }, + { + name: "OrganizationID", + id: &ObjectId{Descriptor: &OrganizationId{OrganizationID: "<org_id>"}}, + }, + { + name: "SpaceId", + id: &ObjectId{Descriptor: &SpaceId{SpaceID: "<space_id>"}}, + }, + { + name: "ClientID", + id: &ObjectId{Descriptor: &ClientId{ + SpaceId: SpaceId{SpaceID: "<space_id>"}, + ClientID: "<client_id>", + }}, + }, + { + name: "RoleID", + id: &ObjectId{Descriptor: &RoleId{ + SpaceId: SpaceId{SpaceID: "<space_id>"}, + RoleID: "<role_id>", + }}, + }, + { + name: "EnvironmentID", + id: &ObjectId{Descriptor: &EnvironmentId{ + SpaceId: SpaceId{SpaceID: "<space_id>"}, + EnvironmentID: "<env_id>", + }}, + }, + { + name: "CollectionId", + id: &ObjectId{Descriptor: &CollectionId{ + EnvironmentId: EnvironmentId{ + SpaceId: SpaceId{SpaceID: "<space_id>"}, + EnvironmentID: "<env_id>", + }, + CollectionID: "<collection_id>", + }}, + }, + { + name: "Schema ID", + id: &ObjectId{Descriptor: &SchemaId{ + EnvironmentId: EnvironmentId{ + SpaceId: SpaceId{SpaceID: "<space_id>"}, + EnvironmentID: "<env_id>", + }, + CollectionID: "<collection_id>", + }}, + }, + { + name: "ItemId", + id: &ObjectId{Descriptor: &ItemId{ + CollectionId: CollectionId{ + EnvironmentId: EnvironmentId{ + SpaceId: SpaceId{SpaceID: "<space_id>"}, + EnvironmentID: "<env_id>", + }, + CollectionID: "<collection_id>", + }, + ItemID: "<item_id>", + }}, + }, + { + name: "RevisionID", + id: &ObjectId{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: &ObjectId{Descriptor: &FieldId{ + ItemId: ItemId{ + CollectionId: CollectionId{ + EnvironmentId: EnvironmentId{ + SpaceId: SpaceId{SpaceID: "<space_id>"}, + EnvironmentID: "<env_id>", + }, + CollectionID: "<collection_id>", + }, + ItemID: "<item_id>", + }, + Field: "<field_name>", + }}, + }, + { + name: "SystemID", + id: &ObjectId{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/organization.go b/id/organization.go index fe9d22837977b486e78689ebee5f426bdee80fcf..03fd47fc9f51fbdb67c219c6a636f0f117d032c7 100644 --- a/id/organization.go +++ b/id/organization.go @@ -1,49 +1,62 @@ package id +import ( + "fmt" + + "git.perx.ru/perxis/perxis-go/pkg/organizations" +) + const ( Organization = "organization" OrganizationsPrefix = "orgs" ) -type OrganizationID struct { +var _ Descriptor = &OrganizationId{} + +type OrganizationId struct { OrganizationID string `json:"organization_id,omitempty" bson:"organization_id,omitempty"` } -func (t *OrganizationID) Type() string { return Organization } +func (id *OrganizationId) New() Descriptor { + return &OrganizationId{} +} + +func (id *OrganizationId) Type() string { return Organization } -func (t *OrganizationID) String() string { - return Join(OrganizationsPrefix, t.OrganizationID) +func (id *OrganizationId) String() string { + return Join(OrganizationsPrefix, id.OrganizationID) } -func (t *OrganizationID) ToMap() map[string]any { - return map[string]any{ - "organization_id": t.OrganizationID, - "type": Organization, +func (id *OrganizationId) FromParts(parts []string) error { + if len(parts) != 2 || parts[0] != OrganizationsPrefix { + return ErrInvalidID } + id.OrganizationID = parts[1] + return nil } -func (t *OrganizationID) FromMap(m map[string]any) error { - t.OrganizationID = m["organization_id"].(string) - return nil +func (id *OrganizationId) Map() map[string]any { + return map[string]any{ + "organization_id": id.OrganizationID, + "type": id.Type(), + } } -func (t *OrganizationID) Validate() error { - if t.OrganizationID == "" { - return ErrInvalidID +func (id *OrganizationId) FromMap(m map[string]any) error { + id.OrganizationID, _ = m["organization_id"].(string) + if id.OrganizationID == "" { + return fmt.Errorf("%w: OrganizationId required", ErrInvalidID) } return nil } -func parseOrganizationID(parts []string) (*OrganizationID, error) { - var id OrganizationID - if len(parts) != 2 || parts[0] != OrganizationsPrefix { - return nil, ErrInvalidID +func (id *OrganizationId) Validate() error { + if id.OrganizationID == "" { + return fmt.Errorf("%w: OrganizationId required", ErrInvalidID) } - - id.OrganizationID = parts[1] - return &id, nil + return nil } -func NewOrganizationID(id string) *ID { - return &ID{Descriptor: &OrganizationID{OrganizationID: id}} +func NewOrganizationId(o organizations.Organization) *ObjectId { + return &ObjectId{Descriptor: &OrganizationId{OrganizationID: o.ID}} } diff --git a/id/registry.go b/id/registry.go new file mode 100644 index 0000000000000000000000000000000000000000..89b779c96f98472f391073283274014ff1075756 --- /dev/null +++ b/id/registry.go @@ -0,0 +1,118 @@ +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", ErrInvalidID, 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("%s: %s", t, ErrInvalidID) +} + +func (r *Registry) FromObject(v interface{}) (*ObjectId, error) { + t := reflect.TypeOf(v) + fmt.Println(t.String()) + fmt.Println(r.handlers) + 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(&SchemaId{}) + r.RegisterDescriptor(&ItemId{}) + r.RegisterDescriptor(&RevisionId{}) + r.RegisterDescriptor(&FieldId{}) + r.RegisterDescriptor(&ClientId{}) + r.RegisterDescriptor(&RoleId{}) + r.RegisterDescriptor(&UserId{}) + r.RegisterDescriptor(&SystemId{}) + r.RegisterDescriptor(&ServiceId{}) + r.RegisterDescriptor(&OrganizationId{}) +} + +func GetRegistry() *Registry { + return registry +} + +func init() { + RegisterSystemIds(registry) +} diff --git a/id/revision.go b/id/revision.go index 0cb417e132fe1683f1e53a081e7c6244820478df..f8a2ecb4b893d71734e72bd1911a32173a3fd17c 100644 --- a/id/revision.go +++ b/id/revision.go @@ -1,61 +1,82 @@ package id +import "fmt" + const ( Revision = "revision" RevisionsPrefix = "revs" ) -type RevisionID struct { - ItemID +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) +var _ Descriptor = &RevisionId{} +func (id *RevisionId) New() Descriptor { + return &RevisionId{} } -func (t *RevisionID) ToMap() map[string]any { - m := t.ItemID.ToMap() - m["rev_id"] = t.RevisionID - m["type"] = Revision - return m +func (id *RevisionId) Type() string { return Revision } + +func (id *RevisionId) String() string { + return Join(id.ItemId.String(), RevisionsPrefix, id.RevisionID) + } -func (t *RevisionID) FromMap(m map[string]any) error { - if err := t.ItemID.FromMap(m); err != nil { +func (id *RevisionId) FromParts(parts []string) error { + if len(parts) != 10 || parts[8] != RevisionsPrefix { + return ErrInvalidID + } + if err := id.ItemId.FromParts(parts[:8]); err != nil { return err } - t.RevisionID = m["rev_id"].(string) + id.RevisionID = parts[9] return nil } -func (t *RevisionID) Validate() error { - if t.RevisionID == "" { - return ErrInvalidID - } - - return t.ItemID.Validate() +func (id *RevisionId) Map() map[string]any { + m := id.ItemId.Map() + m["rev_id"] = id.RevisionID + m["type"] = Revision + return m } -func parseRevisionID(parts []string) (*RevisionID, error) { - if len(parts) != 10 || parts[8] != RevisionsPrefix { - return nil, ErrInvalidID +func (id *RevisionId) FromMap(m map[string]any) error { + id.RevisionID = m["rev_id"].(string) + if id.RevisionID == "" { + return fmt.Errorf("%w: RevisionId required", ErrInvalidID) } - itemID, err := parseItemID(parts[:8]) - if err != nil { - return nil, err + if err := id.ItemId.FromMap(m); err != nil { + return err } + return nil +} - var id RevisionID - id.ItemID = *itemID - id.RevisionID = parts[9] - return &id, nil +func (id *RevisionId) Validate() error { + if id.RevisionID == "" { + return fmt.Errorf("%w: RevisionId required", ErrInvalidID) + } + + return id.ItemId.Validate() } -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}} +func NewRevisionID(spaceID, envID, collID, itemID, id string) *ObjectId { + return &ObjectId{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 index abb6537fc605d3f01f644a67a724cc81e3a6b54b..4e507d92e6443a7bd890fd5ea0414c1fddebb3c3 100644 --- a/id/role.go +++ b/id/role.go @@ -1,61 +1,71 @@ package id +import ( + "fmt" + + "git.perx.ru/perxis/perxis-go/pkg/roles" +) + const ( Role = "role" RolesPrefix = "roles" ) -type RoleID struct { - SpaceID +var _ Descriptor = &RoleId{} + +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 (id *RoleId) New() Descriptor { + return &RoleId{} } -func (t *RoleID) ToMap() map[string]any { - m := t.SpaceID.ToMap() - m["role_id"] = t.RoleID - m["type"] = Role - return m +func (id *RoleId) Type() string { return Role } + +func (id *RoleId) String() string { + return Join(id.SpaceId.String(), RolesPrefix, id.RoleID) } -func (t *RoleID) FromMap(m map[string]any) error { - if err := t.SpaceID.FromMap(m); err != nil { +func (id *RoleId) FromParts(parts []string) error { + if len(parts) != 4 || parts[2] != RolesPrefix { + return ErrInvalidID + } + if err := id.SpaceId.FromParts(parts[:2]); err != nil { return err } - t.RoleID = m["role_id"].(string) + id.RoleID = parts[3] return nil } -func (t *RoleID) Validate() error { - if t.RoleID == "" { - return ErrInvalidID - } - - return t.SpaceID.Validate() +func (id *RoleId) Map() map[string]any { + m := id.SpaceId.Map() + m["role_id"] = id.RoleID + m["type"] = Role + return m } -func parseRoleID(parts []string) (*RoleID, error) { - if len(parts) != 4 || parts[2] != RolesPrefix { - return nil, ErrInvalidID +func (id *RoleId) FromMap(m map[string]any) error { + id.RoleID = m["role_id"].(string) + if id.RoleID == "" { + return fmt.Errorf("%w: RoleID required", ErrInvalidID) } + return id.SpaceId.FromMap(m) +} - spaceID, err := parseSpaceID(parts[:2]) - if err != nil { - return nil, err +func (id *RoleId) Validate() error { + if id.RoleID == "" { + return fmt.Errorf("%w: RoleID required", ErrInvalidID) } - var id RoleID - id.SpaceID = *spaceID - id.RoleID = parts[3] - return &id, nil + return id.SpaceId.Validate() } - -func NewRoleID(spaceID, id string) *ID { - return &ID{Descriptor: &RoleID{SpaceID: SpaceID{SpaceID: spaceID}, RoleID: id}} +func NewRoleId(r roles.Role) *ObjectId { + return &ObjectId{Descriptor: &RoleId{ + SpaceId: SpaceId{ + SpaceID: r.SpaceID, + }, + RoleID: r.ID, + }} } diff --git a/id/schema.go b/id/schema.go index e3afee8c6cf29bca6cae1f1eedd72253c47362a5..b1280f81ca440ae78ce128e471292e26a2db4a97 100644 --- a/id/schema.go +++ b/id/schema.go @@ -1,60 +1,70 @@ package id +import "fmt" + const ( Schema = "schema" SchemaPrefix = "schema" ) -type SchemaID struct { - EnvironmentID +var _ Descriptor = &SchemaId{} + +type SchemaId struct { + EnvironmentId CollectionID string `json:"col_id" bson:"col_id,omitempty"` } -func (t *SchemaID) Type() string { return Schema } +func (id *SchemaId) New() Descriptor { + return &SchemaId{} +} + +func (id *SchemaId) Type() string { return Schema } -func (t *SchemaID) String() string { - return Join(t.EnvironmentID.String(), SchemaPrefix, t.CollectionID) +func (id *SchemaId) String() string { + return Join(id.EnvironmentId.String(), SchemaPrefix, id.CollectionID) } -func (t *SchemaID) ToMap() map[string]any { - m := t.EnvironmentID.ToMap() - m["col_id"] = t.CollectionID +func (id *SchemaId) Map() map[string]any { + m := id.EnvironmentId.Map() + m["col_id"] = id.CollectionID m["type"] = Schema return m } -func (t *SchemaID) FromMap(m map[string]any) error { - if err := t.EnvironmentID.FromMap(m); err != nil { +func (id *SchemaId) FromParts(parts []string) error { + if len(parts) != 6 || parts[4] != SchemaPrefix { + return ErrInvalidID + } + 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 *SchemaID) Validate() error { - if t.CollectionID == "" { - return ErrInvalidID +func (id *SchemaId) FromMap(m map[string]any) error { + id.CollectionID, _ = m["col_id"].(string) + if id.CollectionID == "" { + return fmt.Errorf("%w: SchemaID required", ErrInvalidID) } - - return t.EnvironmentID.Validate() + return id.EnvironmentId.FromMap(m) } -func parseSchemaID(parts []string) (*SchemaID, error) { - if len(parts) != 6 || parts[4] != SchemaPrefix { - return nil, ErrInvalidID +func (id *SchemaId) Validate() error { + if id.CollectionID == "" { + return fmt.Errorf("%w: SchemaID required", 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 + return id.EnvironmentId.Validate() } -func NewSchemaID(spaceID, envID, id string) *ID { - return &ID{Descriptor: &SchemaID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: id}} +func NewSchemaId(spaceID, envID, id string) *ObjectId { + return &ObjectId{Descriptor: &SchemaId{ + EnvironmentId: EnvironmentId{ + SpaceId: SpaceId{ + SpaceID: spaceID, + }, + EnvironmentID: envID, + }, + CollectionID: id, + }} } diff --git a/id/service.go b/id/service.go index 23bb23aa500fbba082e1aae578eae6da5624a5b9..29c44ec635f763b9f35223f710f2d530f2dfb159 100644 --- a/id/service.go +++ b/id/service.go @@ -1,49 +1,58 @@ package id +import "fmt" + const ( Service = "service" ServicesPrefix = "services" ) -type ServiceID struct { +type ServiceId struct { ServiceID string `json:"service_id,omitempty" bson:"service_id,omitempty"` } -func (t *ServiceID) Type() string { return Service } +var _ Descriptor = &ServiceId{} -func (t *ServiceID) String() string { - return Join(ServicesPrefix, t.ServiceID) +func (id *ServiceId) New() Descriptor { + return &ServiceId{} } -func (t *ServiceID) ToMap() map[string]any { - return map[string]any{ - "service_id": t.ServiceID, - "type": Service, - } -} +func (id *ServiceId) Type() string { return Service } -func (t *ServiceID) FromMap(m map[string]any) error { - t.ServiceID = m["service_id"].(string) - return nil +func (id *ServiceId) String() string { + return Join(ServicesPrefix, id.ServiceID) } -func (t *ServiceID) Validate() error { - if t.ServiceID == "" { +func (id *ServiceId) FromParts(parts []string) error { + if len(parts) != 2 || parts[0] != ServicesPrefix { return ErrInvalidID } + id.ServiceID = parts[1] return nil } -func parseServiceID(parts []string) (*ServiceID, error) { - var id ServiceID - if len(parts) != 2 || parts[0] != ServicesPrefix { - return nil, ErrInvalidID +func (id *ServiceId) Map() map[string]any { + return map[string]any{ + "service_id": id.ServiceID, + "type": id.Type(), } +} - id.ServiceID = parts[1] - return &id, nil +func (id *ServiceId) FromMap(m map[string]any) error { + id.ServiceID, _ = m["service_id"].(string) + if id.ServiceID == "" { + return fmt.Errorf("%w: ServiceId required", ErrInvalidID) + } + return nil +} + +func (id *ServiceId) Validate() error { + if id.ServiceID == "" { + return fmt.Errorf("%w: ServiceId required", ErrInvalidID) + } + return nil } -func NewServiceID(id string) *ID { - return &ID{Descriptor: &ServiceID{ServiceID: id}} +func NewServiceID(id string) *ObjectId { + return &ObjectId{Descriptor: &ServiceId{ServiceID: id}} } diff --git a/id/space.go b/id/space.go index 39096673456d74462ef203b10814396fe48b2bcf..a9d88bc66ba8105f00825674a2015ea16779860f 100644 --- a/id/space.go +++ b/id/space.go @@ -1,48 +1,63 @@ package id +import ( + "fmt" + + "git.perx.ru/perxis/perxis-go/pkg/spaces" +) + 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 ErrInvalidID } -} -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", ErrInvalidID) } + return nil +} - id.SpaceID = parts[1] - return &id, nil +func (id *SpaceId) Validate() error { + if id.SpaceID == "" { + return fmt.Errorf("%w: SpaceId required", ErrInvalidID) + } + return nil } -func NewSpaceID(id string) *ID { - return &ID{Descriptor: &SpaceID{SpaceID: id}} + +func NewSpaceId(s spaces.Space) *ObjectId { + return &ObjectId{Descriptor: &SpaceId{SpaceID: s.ID}} } diff --git a/id/system.go b/id/system.go index de2f3c2571896e448f8aad17be32df58b73a6ea4..28eb6e04f4e5e7eff20e2b4218c33f8bdcec1fc9 100644 --- a/id/system.go +++ b/id/system.go @@ -2,21 +2,23 @@ package id const System = "system" -type SystemID struct{} +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 } +var _ Descriptor = &SystemId{} -func parseSystemID(parts []string) (*SystemID, error) { - var id SystemID +func (id *SystemId) New() Descriptor { + return &SystemId{} +} + +func (id *SystemId) Type() string { return System } +func (id *SystemId) String() string { return string(Separator) + System } +func (id *SystemId) FromParts(parts []string) error { if len(parts) != 1 || parts[0] != System { - return nil, ErrInvalidID + return ErrInvalidID } - return &id, nil -} -func NewSystemID() *ID { - return &ID{Descriptor: &SystemID{}} + return nil } +func (id *SystemId) Map() map[string]any { return map[string]any{"type": System} } +func (id *SystemId) FromMap(m map[string]any) error { return nil } +func (id *SystemId) Validate() error { return nil } +func (id *SystemId) NewSystemId() *ObjectId { return &ObjectId{Descriptor: &SystemId{}} } diff --git a/id/system/system.go b/id/system/system.go new file mode 100644 index 0000000000000000000000000000000000000000..77084f5dcb8dc702b2b14337e97cdc97fd80930b --- /dev/null +++ b/id/system/system.go @@ -0,0 +1,97 @@ +package system + +import ( + "context" + "reflect" + + "git.perx.ru/perxis/perxis-go/id" + "git.perx.ru/perxis/perxis-go/pkg/auth" + "git.perx.ru/perxis/perxis-go/pkg/clients" + "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/organizations" + "git.perx.ru/perxis/perxis-go/pkg/roles" + "git.perx.ru/perxis/perxis-go/pkg/spaces" + "git.perx.ru/perxis/perxis-go/pkg/users" +) + +func Handler(obj any) *id.ObjectId { + switch val := obj.(type) { + case *organizations.Organization: + var i id.OrganizationId + i.OrganizationID = val.ID + return id.MustObjectId(&i) + 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: + var i id.UserId + i.UserID = val.ID + return id.MustObjectId(&i) + case *clients.Client: + var i id.ClientId + i.SpaceID = val.SpaceID + i.ClientID = val.ID + return id.MustObjectId(&i) + case *roles.Role: + var i id.RoleId + i.SpaceID = val.SpaceID + i.RoleID = val.ID + return id.MustObjectId(&i) + case *auth.UserPrincipal: + var i id.UserId + i.UserID = val.GetID(context.TODO()) + return id.MustObjectId(&i) + case *auth.ClientPrincipal: + var i id.ClientId + i.ClientID = val.GetID(context.TODO()) + return id.MustObjectId(&i) + case *auth.SystemPrincipal: + return id.MustObjectId(&id.SystemId{}) + case *auth.Anonymous: + var i id.UserId + i.UserID = val.GetID(context.TODO()) + return id.MustObjectId(&i) + } + return nil +} + +// Register registers object handler for system types into the provided Registry. +func Register(r *id.Registry) { + r.RegisterObjectHandler(reflect.TypeOf(&organizations.Organization{}), Handler) + 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) + r.RegisterObjectHandler(reflect.TypeOf(&clients.Client{}), Handler) + r.RegisterObjectHandler(reflect.TypeOf(&roles.Role{}), Handler) + r.RegisterObjectHandler(reflect.TypeOf(&users.User{}), Handler) + r.RegisterObjectHandler(reflect.TypeOf(&auth.UserPrincipal{}), Handler) + r.RegisterObjectHandler(reflect.TypeOf(&auth.ClientPrincipal{}), Handler) + r.RegisterObjectHandler(reflect.TypeOf(&auth.SystemPrincipal{}), Handler) + r.RegisterObjectHandler(reflect.TypeOf(&auth.Anonymous{}), Handler) +} + +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 0000000000000000000000000000000000000000..f97e50e49068173b5aa1968de33abc4d8a44817b --- /dev/null +++ b/id/test/object_id_test.go @@ -0,0 +1,801 @@ +package test + +import ( + "testing" + + "git.perx.ru/perxis/perxis-go/id" + _ "git.perx.ru/perxis/perxis-go/id/system" + "git.perx.ru/perxis/perxis-go/pkg/clients" + "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/organizations" + "git.perx.ru/perxis/perxis-go/pkg/roles" + "git.perx.ru/perxis/perxis-go/pkg/spaces" + "git.perx.ru/perxis/perxis-go/pkg/users" + "github.com/stretchr/testify/require" +) + +func Test_OrganizationId(t *testing.T) { + + tests := []struct { + name string + in any + out string + result *id.ObjectId + err error + }{ + { + name: "valid string", + in: "/orgs/<org_id>", + result: &id.ObjectId{Descriptor: &id.OrganizationId{OrganizationID: "<org_id>"}}, + }, + { + name: "valid object", + in: &organizations.Organization{ID: "<org_id>"}, + out: "/orgs/<org_id>", + result: &id.ObjectId{Descriptor: &id.OrganizationId{OrganizationID: "<org_id>"}}, + }, + { + name: "valid map", + in: map[string]any{"type": "organization", "organization_id": "<org_id>"}, + out: "/orgs/<org_id>", + result: &id.ObjectId{Descriptor: &id.OrganizationId{OrganizationID: "<org_id>"}}, + }, + { + name: "invalid map", + in: map[string]any{"type": "organization"}, + out: "/orgs/<org_id>", + result: &id.ObjectId{Descriptor: &id.OrganizationId{OrganizationID: "<org_id>"}}, + err: id.ErrInvalidID, + }, + } + + 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_ServiceId(t *testing.T) { + + tests := []struct { + name string + in any + out string + result *id.ObjectId + err error + }{ + { + name: "valid string", + in: "/services/<service_id>", + result: &id.ObjectId{Descriptor: &id.ServiceId{ServiceID: "<service_id>"}}, + }, + { + name: "valid map", + in: map[string]any{"type": "service", "service_id": "<service_id>"}, + out: "/services/<service_id>", + result: &id.ObjectId{Descriptor: &id.ServiceId{ServiceID: "<service_id>"}}, + }, + { + name: "invalid map", + in: map[string]any{"type": "service"}, + out: "/services/<service_id>", + result: &id.ObjectId{Descriptor: &id.ServiceId{ServiceID: "<service_id>"}}, + err: id.ErrInvalidID, + }, + } + + 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_UserId(t *testing.T) { + + tests := []struct { + name string + in any + out string + result *id.ObjectId + err error + }{ + { + name: "valid string", + in: "/users/<user_id>", + result: &id.ObjectId{Descriptor: &id.UserId{UserID: "<user_id>"}}, + }, + { + name: "valid object", + in: &users.User{ID: "<user_id>"}, + out: "/users/<user_id>", + result: &id.ObjectId{Descriptor: &id.UserId{UserID: "<user_id>"}}, + }, + { + name: "valid map", + in: map[string]any{"type": "user", "user_id": "<user_id>"}, + out: "/users/<user_id>", + result: &id.ObjectId{Descriptor: &id.UserId{UserID: "<user_id>"}}, + }, + { + name: "invalid map", + in: map[string]any{"type": "user"}, + out: "/users/<user_id>", + result: &id.ObjectId{Descriptor: &id.UserId{UserID: "<user_id>"}}, + err: id.ErrInvalidID, + }, + } + + 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_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.ErrInvalidID, + }, + } + + 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.ErrInvalidID, + }, + { + 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.ErrInvalidID, + }, + { + 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.ErrInvalidID, + }, + } + + 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_ClientId(t *testing.T) { + + tests := []struct { + name string + in any + out string + result *id.ObjectId + err error + }{ + { + name: "valid string", + in: "/spaces/<space_id>/clients/<client_id>", + result: &id.ObjectId{Descriptor: &id.ClientId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + ClientID: "<client_id>", + }}, + }, + { + name: "invalid string", + in: "/clients/<client_id>", + result: &id.ObjectId{Descriptor: &id.ClientId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + ClientID: "<client_id>", + }}, + err: id.ErrInvalidID, + }, + { + name: "valid object", + in: &clients.Client{SpaceID: "<space_id>", ID: "<client_id>"}, + out: "/spaces/<space_id>/clients/<client_id>", + result: &id.ObjectId{Descriptor: &id.ClientId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + ClientID: "<client_id>", + }}, + }, + { + name: "valid map", + in: map[string]any{"type": "client", "space_id": "<space_id>", "client_id": "<client_id>"}, + out: "/spaces/<space_id>/clients/<client_id>", + result: &id.ObjectId{Descriptor: &id.ClientId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + ClientID: "<client_id>", + }}, + }, + { + name: "invalid map 1", + in: map[string]any{"type": "client", "space_id": "<space_id>"}, + out: "/spaces/<space_id>/clients/<client_id>", + result: &id.ObjectId{Descriptor: &id.ClientId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + ClientID: "<client_id>", + }}, + err: id.ErrInvalidID, + }, + { + name: "invalid map 2", + in: map[string]any{"type": "client", "client_id": "<client_id>"}, + out: "/spaces/<space_id>/clients/<client_id>", + result: &id.ObjectId{Descriptor: &id.ClientId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + ClientID: "<client_id>", + }}, + err: id.ErrInvalidID, + }, + } + + 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_RoleId(t *testing.T) { + + tests := []struct { + name string + in any + out string + result *id.ObjectId + err error + }{ + { + name: "valid string", + in: "/spaces/<space_id>/roles/<role_id>", + result: &id.ObjectId{Descriptor: &id.RoleId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + RoleID: "<role_id>", + }}, + }, + { + name: "invalid string", + in: "/roles/<role_id>", + result: &id.ObjectId{Descriptor: &id.RoleId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + RoleID: "<role_id>", + }}, + err: id.ErrInvalidID, + }, + { + name: "valid object", + in: &roles.Role{SpaceID: "<space_id>", ID: "<role_id>"}, + out: "/spaces/<space_id>/roles/<role_id>", + result: &id.ObjectId{Descriptor: &id.RoleId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + RoleID: "<role_id>", + }}, + }, + { + name: "valid map", + in: map[string]any{"type": "role", "space_id": "<space_id>", "role_id": "<role_id>"}, + out: "/spaces/<space_id>/roles/<role_id>", + result: &id.ObjectId{Descriptor: &id.RoleId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + RoleID: "<role_id>", + }}, + }, + { + name: "invalid map 1", + in: map[string]any{"type": "client", "space_id": "<space_id>"}, + out: "/spaces/<space_id>/roles/<role_id>", + result: &id.ObjectId{Descriptor: &id.RoleId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + RoleID: "<role_id>", + }}, + err: id.ErrInvalidID, + }, + { + name: "invalid map 2", + in: map[string]any{"type": "role", "role_id": "<role_id>"}, + out: "/spaces/<space_id>/roles/<role_id>", + result: &id.ObjectId{Descriptor: &id.RoleId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + RoleID: "<role_id>", + }}, + err: id.ErrInvalidID, + }, + } + + 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>", + }}, + }, + } + + 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_SchemaId(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>/schema/<collection_id>", + result: &id.ObjectId{Descriptor: &id.SchemaId{ + EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"}, + CollectionID: "<collection_id>", + }}, + }, + { + name: "valid map", + in: map[string]any{"type": "schema", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>"}, + out: "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>", + result: &id.ObjectId{Descriptor: &id.SchemaId{ + EnvironmentId: id.EnvironmentId{SpaceId: id.SpaceId{SpaceID: "<space_id>"}, EnvironmentID: "<env_id>"}, + CollectionID: "<collection_id>", + }}, + }, + } + + 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>", + }}, + }, + } + + 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>", + }}, + }, + } + + 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_RevisionId(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>/revs/<rev_id>", + result: &id.ObjectId{Descriptor: &id.RevisionId{ + ItemId: id.ItemId{ + CollectionId: id.CollectionId{ + EnvironmentId: id.EnvironmentId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + EnvironmentID: "<env_id>", + }, + CollectionID: "<collection_id>", + }, + ItemID: "<item_id>", + }, + RevisionID: "<rev_id>", + }}, + }, + { + name: "valid map", + in: map[string]any{"type": "revision", "space_id": "<space_id>", "env_id": "<env_id>", "col_id": "<collection_id>", "item_id": "<item_id>", "rev_id": "<rev_id>"}, + out: "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>", + result: &id.ObjectId{Descriptor: &id.RevisionId{ + ItemId: id.ItemId{ + CollectionId: id.CollectionId{ + EnvironmentId: id.EnvironmentId{ + SpaceId: id.SpaceId{SpaceID: "<space_id>"}, + EnvironmentID: "<env_id>", + }, + CollectionID: "<collection_id>", + }, + ItemID: "<item_id>", + }, + RevisionID: "<rev_id>", + }}, + }, + } + + 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()) + } + }) + } +} diff --git a/id/user.go b/id/user.go index c76f6c9fa0e8ce440a1ef15e36bbb82a73301be5..8b4100b01039f5ebd65722e2521645304df31b04 100644 --- a/id/user.go +++ b/id/user.go @@ -1,49 +1,62 @@ package id +import ( + "fmt" + + "git.perx.ru/perxis/perxis-go/pkg/users" +) + const ( User = "user" UsersPrefix = "users" ) -type UserID struct { +type UserId struct { UserID string `json:"user_id,omitempty" bson:"user_id,omitempty"` } -func (t *UserID) Type() string { return User } +var _ Descriptor = &UserId{} -func (t *UserID) String() string { - return Join(UsersPrefix, t.UserID) +func (id *UserId) New() Descriptor { + return &UserId{} } -func (t *UserID) ToMap() map[string]any { - return map[string]any{ - "user_id": t.UserID, - "type": User, - } -} +func (id *UserId) Type() string { return User } -func (t *UserID) FromMap(m map[string]any) error { - t.UserID = m["user_id"].(string) - return nil +func (id *UserId) String() string { + return Join(UsersPrefix, id.UserID) } -func (t *UserID) Validate() error { - if t.UserID == "" { +func (id *UserId) FromParts(parts []string) error { + if len(parts) != 2 || parts[0] != UsersPrefix { return ErrInvalidID } + id.UserID = parts[1] return nil } -func parseUserID(parts []string) (*UserID, error) { - var id UserID - if len(parts) != 2 || parts[0] != UsersPrefix { - return nil, ErrInvalidID +func (id *UserId) Map() map[string]any { + return map[string]any{ + "user_id": id.UserID, + "type": id.Type(), } +} - id.UserID = parts[1] - return &id, nil +func (id *UserId) FromMap(m map[string]any) error { + id.UserID, _ = m["user_id"].(string) + if id.UserID == "" { + return fmt.Errorf("%w: UserId required", ErrInvalidID) + } + return nil +} + +func (id *UserId) Validate() error { + if id.UserID == "" { + return fmt.Errorf("%w: UserId required", ErrInvalidID) + } + return nil } -func NewUserID(id string) *ID { - return &ID{Descriptor: &UserID{UserID: id}} +func NewUserId(u users.User) *ObjectId { + return &ObjectId{Descriptor: &UserId{UserID: u.ID}} } diff --git a/log/log.go b/log/log.go index d21ddbcaaa262c4c16e95a35cd098f77ea4a53cb..05aff704243a60e52c70a7ee57cdb50f9c5884e7 100644 --- a/log/log.go +++ b/log/log.go @@ -27,17 +27,17 @@ func (l Level) String() string { } type Entry struct { - ID string `json:"id" bson:"id" mapstructure:"id"` - Timestamp time.Time `json:"timestamp,omitempty" bson:"timestamp,omitempty" mapstructure:"timestamp,omitempty"` - LogLevel Level `json:"log_level,omitempty" bson:"log_level,omitempty" mapstructure:"log_level,omitempty"` - Message string `json:"message,omitempty" bson:"message,omitempty" mapstructure:"message,omitempty"` - Category string `json:"category,omitempty" bson:"category,omitempty" mapstructure:"category,omitempty"` - Component string `json:"component,omitempty" bson:"component,omitempty" mapstructure:"component,omitempty"` - Event string `json:"event,omitempty" bson:"event,omitempty" mapstructure:"event,omitempty"` - ObjectID *id.ID `json:"object_id,omitempty" bson:"object_id,omitempty" mapstructure:"object_id,omitempty"` - CallerID *id.ID `json:"caller_id,omitempty" bson:"caller_id,omitempty" mapstructure:"caller_id,omitempty"` - Attr interface{} `json:"attr,omitempty" bson:"attr,omitempty" mapstructure:"attr,omitempty"` - Tags []string `json:"tags,omitempty" bson:"tags,omitempty" mapstructure:"tags,omitempty"` + ID string `json:"id" bson:"id" mapstructure:"id"` + Timestamp time.Time `json:"timestamp,omitempty" bson:"timestamp,omitempty" mapstructure:"timestamp,omitempty"` + LogLevel Level `json:"log_level,omitempty" bson:"log_level,omitempty" mapstructure:"log_level,omitempty"` + Message string `json:"message,omitempty" bson:"message,omitempty" mapstructure:"message,omitempty"` + Category string `json:"category,omitempty" bson:"category,omitempty" mapstructure:"category,omitempty"` + Component string `json:"component,omitempty" bson:"component,omitempty" mapstructure:"component,omitempty"` + Event string `json:"event,omitempty" bson:"event,omitempty" mapstructure:"event,omitempty"` + ObjectID *id.ObjectId `json:"object_id,omitempty" bson:"object_id,omitempty" mapstructure:"object_id,omitempty"` + CallerID *id.ObjectId `json:"caller_id,omitempty" bson:"caller_id,omitempty" mapstructure:"caller_id,omitempty"` + Attr interface{} `json:"attr,omitempty" bson:"attr,omitempty" mapstructure:"attr,omitempty"` + Tags []string `json:"tags,omitempty" bson:"tags,omitempty" mapstructure:"tags,omitempty"` } //func convertInterfaceToAny(v interface{}) (*any.Any, error) { @@ -84,10 +84,10 @@ func EntryFromPB(request *pb.LogEntry) *Entry { } if request.ObjectId != "" { - logEntry.ObjectID, _ = id.Parse(request.ObjectId) + logEntry.ObjectID, _ = id.NewObjectId(request.ObjectId) } if request.CallerId != "" { - logEntry.CallerID, _ = id.Parse(request.CallerId) + logEntry.CallerID, _ = id.NewObjectId(request.CallerId) } if request.Attr != nil { logEntry.Attr = request.Attr // todo: как с этим работать? @@ -111,11 +111,11 @@ func (e *Entry) ToMap() map[string]any { } if e.ObjectID != nil { res["object_id"] = e.ObjectID.String() - res["object"] = e.ObjectID.ToMap() + res["object"] = e.ObjectID.Map() } if e.CallerID != nil { res["caller_id"] = e.CallerID.String() - res["caller"] = e.CallerID.ToMap() + res["caller"] = e.CallerID.Map() } if e.Attr != nil { res["attr"] = e.Attr diff --git a/log/log_test.go b/log/log_test.go index 08d78f594dcaeae0c72557401f70c4d026e918b2..72ab7d3c10459a8be5464f9bd51546f33df3e5f7 100644 --- a/log/log_test.go +++ b/log/log_test.go @@ -17,8 +17,8 @@ func TestEntry_ToMap(t *testing.T) { Category string Component string Event string - ObjectId *id.ID - CallerId *id.ID + ObjectId *id.ObjectId + CallerId *id.ObjectId Attr interface{} Tags []string } @@ -37,8 +37,8 @@ func TestEntry_ToMap(t *testing.T) { "", "", "", - id.NewEnvironmentID("<space_id>", "<env_id>"), - id.NewUserID("<user_id>"), + id.MustObjectId("/spaces/<space_id>/envs/<env_id>"), + id.MustObjectId("/users/<user_id>"), nil, nil, }, diff --git a/log/middleware/error_logging_middleware.go b/log/middleware/error_logging_middleware.go new file mode 100644 index 0000000000000000000000000000000000000000..de13dcc4fa1d3f8edde99896cdfc24fe1a728128 --- /dev/null +++ b/log/middleware/error_logging_middleware.go @@ -0,0 +1,61 @@ +// Code generated by gowrap. DO NOT EDIT. +// template: ../../assets/templates/middleware/error_log +// gowrap: http://github.com/hexdigest/gowrap + +package middleware + +//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/log -i Service -t ../../assets/templates/middleware/error_log -o error_logging_middleware.go -l "" + +import ( + "context" + + "git.perx.ru/perxis/perxis-go/log" + "git.perx.ru/perxis/perxis-go/pkg/options" + "go.uber.org/zap" +) + +// errorLoggingMiddleware implements log.Service that is instrumented with logging +type errorLoggingMiddleware struct { + logger *zap.Logger + next log.Service +} + +// ErrorLoggingMiddleware instruments an implementation of the log.Service with simple logging +func ErrorLoggingMiddleware(logger *zap.Logger) Middleware { + return func(next log.Service) log.Service { + return &errorLoggingMiddleware{ + next: next, + logger: logger, + } + } +} + +func (m *errorLoggingMiddleware) Delete(ctx context.Context, filter *log.Filter) (err error) { + logger := m.logger + defer func() { + if err != nil { + logger.Warn("response error", zap.Error(err)) + } + }() + return m.next.Delete(ctx, filter) +} + +func (m *errorLoggingMiddleware) Find(ctx context.Context, filter *log.Filter, options *options.FindOptions) (fp1 *log.FindResult, err error) { + logger := m.logger + defer func() { + if err != nil { + logger.Warn("response error", zap.Error(err)) + } + }() + return m.next.Find(ctx, filter, options) +} + +func (m *errorLoggingMiddleware) Log(ctx context.Context, entries []*log.Entry) (err error) { + logger := m.logger + defer func() { + if err != nil { + logger.Warn("response error", zap.Error(err)) + } + }() + return m.next.Log(ctx, entries) +} diff --git a/log/middleware/logging_middleware.go b/log/middleware/logging_middleware.go new file mode 100644 index 0000000000000000000000000000000000000000..f693795ba6b45bc915212f968c6347a0aa7aed61 --- /dev/null +++ b/log/middleware/logging_middleware.go @@ -0,0 +1,145 @@ +// Code generated by gowrap. DO NOT EDIT. +// template: ../../assets/templates/middleware/access_log +// gowrap: http://github.com/hexdigest/gowrap + +package middleware + +//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/log -i Service -t ../../assets/templates/middleware/access_log -o logging_middleware.go -l "" + +import ( + "context" + "fmt" + "time" + + "git.perx.ru/perxis/perxis-go/log" + "git.perx.ru/perxis/perxis-go/pkg/auth" + "git.perx.ru/perxis/perxis-go/pkg/options" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// loggingMiddleware implements log.Service that is instrumented with logging +type loggingMiddleware struct { + logger *zap.Logger + next log.Service +} + +// LoggingMiddleware instruments an implementation of the log.Service with simple logging +func LoggingMiddleware(logger *zap.Logger) Middleware { + return func(next log.Service) log.Service { + return &loggingMiddleware{ + next: next, + logger: logger, + } + } +} + +func (m *loggingMiddleware) Delete(ctx context.Context, filter *log.Filter) (err error) { + begin := time.Now() + var fields []zapcore.Field + for k, v := range map[string]interface{}{ + "ctx": ctx, + "filter": filter} { + if k == "ctx" { + fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx)))) + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Delete.Request", fields...) + + err = m.next.Delete(ctx, filter) + + fields = []zapcore.Field{ + zap.Duration("time", time.Since(begin)), + } + + for k, v := range map[string]interface{}{ + "err": err} { + if k == "err" { + err, _ := v.(error) + fields = append(fields, zap.Error(err)) + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Delete.Response", fields...) + + return err +} + +func (m *loggingMiddleware) Find(ctx context.Context, filter *log.Filter, options *options.FindOptions) (fp1 *log.FindResult, err error) { + begin := time.Now() + var fields []zapcore.Field + for k, v := range map[string]interface{}{ + "ctx": ctx, + "filter": filter, + "options": options} { + if k == "ctx" { + fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx)))) + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Find.Request", fields...) + + fp1, err = m.next.Find(ctx, filter, options) + + fields = []zapcore.Field{ + zap.Duration("time", time.Since(begin)), + } + + for k, v := range map[string]interface{}{ + "fp1": fp1, + "err": err} { + if k == "err" { + err, _ := v.(error) + fields = append(fields, zap.Error(err)) + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Find.Response", fields...) + + return fp1, err +} + +func (m *loggingMiddleware) Log(ctx context.Context, entries []*log.Entry) (err error) { + begin := time.Now() + var fields []zapcore.Field + for k, v := range map[string]interface{}{ + "ctx": ctx, + "entries": entries} { + if k == "ctx" { + fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx)))) + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Log.Request", fields...) + + err = m.next.Log(ctx, entries) + + fields = []zapcore.Field{ + zap.Duration("time", time.Since(begin)), + } + + for k, v := range map[string]interface{}{ + "err": err} { + if k == "err" { + err, _ := v.(error) + fields = append(fields, zap.Error(err)) + continue + } + fields = append(fields, zap.Reflect(k, v)) + } + + m.logger.Debug("Log.Response", fields...) + + return err +} diff --git a/log/middleware/middleware.go b/log/middleware/middleware.go new file mode 100644 index 0000000000000000000000000000000000000000..2fa83f77f251603392126319e61e27158b0dc3cc --- /dev/null +++ b/log/middleware/middleware.go @@ -0,0 +1,28 @@ +// Code generated by gowrap. DO NOT EDIT. +// template: ../../assets/templates/middleware/middleware +// gowrap: http://github.com/hexdigest/gowrap + +package middleware + +//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/log -i Service -t ../../assets/templates/middleware/middleware -o middleware.go -l "" + +import ( + "git.perx.ru/perxis/perxis-go/log" + "go.uber.org/zap" +) + +type Middleware func(log.Service) log.Service + +func WithLog(s log.Service, logger *zap.Logger, log_access bool) log.Service { + if logger == nil { + logger = zap.NewNop() + } + + logger = logger.Named("Service") + s = ErrorLoggingMiddleware(logger)(s) + if log_access { + s = LoggingMiddleware(logger)(s) + } + s = RecoveringMiddleware(logger)(s) + return s +} diff --git a/log/middleware/recovering_middleware.go b/log/middleware/recovering_middleware.go new file mode 100644 index 0000000000000000000000000000000000000000..b8b3de39570f3fdece8969edb24c8f4a9dbcb0cb --- /dev/null +++ b/log/middleware/recovering_middleware.go @@ -0,0 +1,68 @@ +// Code generated by gowrap. DO NOT EDIT. +// template: ../../assets/templates/middleware/recovery +// gowrap: http://github.com/hexdigest/gowrap + +package middleware + +//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/log -i Service -t ../../assets/templates/middleware/recovery -o recovering_middleware.go -l "" + +import ( + "context" + "fmt" + + "git.perx.ru/perxis/perxis-go/log" + "git.perx.ru/perxis/perxis-go/pkg/options" + "go.uber.org/zap" +) + +// recoveringMiddleware implements log.Service that is instrumented with logging +type recoveringMiddleware struct { + logger *zap.Logger + next log.Service +} + +// RecoveringMiddleware instruments an implementation of the log.Service with simple logging +func RecoveringMiddleware(logger *zap.Logger) Middleware { + return func(next log.Service) log.Service { + return &recoveringMiddleware{ + next: next, + logger: logger, + } + } +} + +func (m *recoveringMiddleware) Delete(ctx context.Context, filter *log.Filter) (err error) { + logger := m.logger + defer func() { + if r := recover(); r != nil { + logger.Error("panic", zap.Error(fmt.Errorf("%v", r))) + err = fmt.Errorf("%v", r) + } + }() + + return m.next.Delete(ctx, filter) +} + +func (m *recoveringMiddleware) Find(ctx context.Context, filter *log.Filter, options *options.FindOptions) (fp1 *log.FindResult, err error) { + logger := m.logger + defer func() { + if r := recover(); r != nil { + logger.Error("panic", zap.Error(fmt.Errorf("%v", r))) + err = fmt.Errorf("%v", r) + } + }() + + return m.next.Find(ctx, filter, options) +} + +func (m *recoveringMiddleware) Log(ctx context.Context, entries []*log.Entry) (err error) { + logger := m.logger + defer func() { + if r := recover(); r != nil { + logger.Error("panic", zap.Error(fmt.Errorf("%v", r))) + err = fmt.Errorf("%v", r) + } + }() + + return m.next.Log(ctx, entries) +} diff --git a/log/middleware/telemetry_middleware.go b/log/middleware/telemetry_middleware.go new file mode 100644 index 0000000000000000000000000000000000000000..f5ba0676cf727ae7d70809b789002b61afe70bf3 --- /dev/null +++ b/log/middleware/telemetry_middleware.go @@ -0,0 +1,221 @@ +// Code generated by gowrap. DO NOT EDIT. +// template: ..\..\..\assets\templates\middleware\telemetry +// gowrap: http://github.com/hexdigest/gowrap + +package middleware + +//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collaborators -i log -i Service -t ../../assets/templates/middleware/telemetry -o telemetry_middleware.go -l "" + +// source template: https://github.com/hexdigest/gowrap/blob/master/templates/opentelemetry + +import ( + "context" + "time" + + "git.perx.ru/perxis/perxis-go/pkg/collaborators" + "git.perx.ru/perxis/perxis-go/pkg/telemetry/metrics" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + otelmetric "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" +) + +// telemetryMiddleware implements collaborators.Collaborators interface instrumented with opentracing spans +type telemetryMiddleware struct { + collaborators.Collaborators + _instance string + requestMetrics *metrics.RequestMetrics + _spanDecorator func(span trace.Span, params, results map[string]interface{}) +} + +// TelemetryMiddleware returns telemetryMiddleware +func TelemetryMiddleware(base collaborators.Collaborators, instance string, spanDecorator ...func(span trace.Span, params, results map[string]interface{})) telemetryMiddleware { + requestMetrics, err := metrics.GetRequestMetrics() + if err != nil { + panic(err) + } + + d := telemetryMiddleware{ + Collaborators: base, + _instance: instance, + requestMetrics: requestMetrics, + } + + if len(spanDecorator) > 0 && spanDecorator[0] != nil { + d._spanDecorator = spanDecorator[0] + } + + return d +} + +// Get implements collaborators.Collaborators +func (_d telemetryMiddleware) Get(ctx context.Context, spaceId string, subject string) (role string, err error) { + attributes := otelmetric.WithAttributeSet(attribute.NewSet( + attribute.String("service", "Collaborators"), + attribute.String("method", "Get"), + )) + + _d.requestMetrics.Total.Add(ctx, 1, attributes) + + start := time.Now() + ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Collaborators.Get") + + defer func() { + _d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes) + + if _d._spanDecorator != nil { + _d._spanDecorator(_span, map[string]interface{}{ + "ctx": ctx, + "spaceId": spaceId, + "subject": subject}, map[string]interface{}{ + "role": role, + "err": err}) + } else if err != nil { + _d.requestMetrics.FailedTotal.Add(ctx, 1, attributes) + + _span.RecordError(err) + _span.SetAttributes(attribute.String("event", "error")) + _span.SetAttributes(attribute.String("message", err.Error())) + } + + _span.End() + }() + return _d.Collaborators.Get(ctx, spaceId, subject) +} + +// ListCollaborators implements collaborators.Collaborators +func (_d telemetryMiddleware) ListCollaborators(ctx context.Context, spaceId string) (collaborators []*collaborators.Collaborator, err error) { + attributes := otelmetric.WithAttributeSet(attribute.NewSet( + attribute.String("service", "Collaborators"), + attribute.String("method", "ListCollaborators"), + )) + + _d.requestMetrics.Total.Add(ctx, 1, attributes) + + start := time.Now() + ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Collaborators.ListCollaborators") + + defer func() { + _d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes) + + if _d._spanDecorator != nil { + _d._spanDecorator(_span, map[string]interface{}{ + "ctx": ctx, + "spaceId": spaceId}, map[string]interface{}{ + "collaborators": collaborators, + "err": err}) + } else if err != nil { + _d.requestMetrics.FailedTotal.Add(ctx, 1, attributes) + + _span.RecordError(err) + _span.SetAttributes(attribute.String("event", "error")) + _span.SetAttributes(attribute.String("message", err.Error())) + } + + _span.End() + }() + return _d.Collaborators.ListCollaborators(ctx, spaceId) +} + +// ListSpaces implements collaborators.Collaborators +func (_d telemetryMiddleware) ListSpaces(ctx context.Context, subject string) (spaces []*collaborators.Collaborator, err error) { + attributes := otelmetric.WithAttributeSet(attribute.NewSet( + attribute.String("service", "Collaborators"), + attribute.String("method", "ListSpaces"), + )) + + _d.requestMetrics.Total.Add(ctx, 1, attributes) + + start := time.Now() + ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Collaborators.ListSpaces") + + defer func() { + _d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes) + + if _d._spanDecorator != nil { + _d._spanDecorator(_span, map[string]interface{}{ + "ctx": ctx, + "subject": subject}, map[string]interface{}{ + "spaces": spaces, + "err": err}) + } else if err != nil { + _d.requestMetrics.FailedTotal.Add(ctx, 1, attributes) + + _span.RecordError(err) + _span.SetAttributes(attribute.String("event", "error")) + _span.SetAttributes(attribute.String("message", err.Error())) + } + + _span.End() + }() + return _d.Collaborators.ListSpaces(ctx, subject) +} + +// Remove implements collaborators.Collaborators +func (_d telemetryMiddleware) Remove(ctx context.Context, spaceId string, subject string) (err error) { + attributes := otelmetric.WithAttributeSet(attribute.NewSet( + attribute.String("service", "Collaborators"), + attribute.String("method", "Remove"), + )) + + _d.requestMetrics.Total.Add(ctx, 1, attributes) + + start := time.Now() + ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Collaborators.Remove") + + defer func() { + _d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes) + + if _d._spanDecorator != nil { + _d._spanDecorator(_span, map[string]interface{}{ + "ctx": ctx, + "spaceId": spaceId, + "subject": subject}, map[string]interface{}{ + "err": err}) + } else if err != nil { + _d.requestMetrics.FailedTotal.Add(ctx, 1, attributes) + + _span.RecordError(err) + _span.SetAttributes(attribute.String("event", "error")) + _span.SetAttributes(attribute.String("message", err.Error())) + } + + _span.End() + }() + return _d.Collaborators.Remove(ctx, spaceId, subject) +} + +// Set implements collaborators.Collaborators +func (_d telemetryMiddleware) Set(ctx context.Context, spaceId string, subject string, role string) (err error) { + attributes := otelmetric.WithAttributeSet(attribute.NewSet( + attribute.String("service", "Collaborators"), + attribute.String("method", "Set"), + )) + + _d.requestMetrics.Total.Add(ctx, 1, attributes) + + start := time.Now() + ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Collaborators.Set") + + defer func() { + _d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes) + + if _d._spanDecorator != nil { + _d._spanDecorator(_span, map[string]interface{}{ + "ctx": ctx, + "spaceId": spaceId, + "subject": subject, + "role": role}, map[string]interface{}{ + "err": err}) + } else if err != nil { + _d.requestMetrics.FailedTotal.Add(ctx, 1, attributes) + + _span.RecordError(err) + _span.SetAttributes(attribute.String("event", "error")) + _span.SetAttributes(attribute.String("message", err.Error())) + } + + _span.End() + }() + return _d.Collaborators.Set(ctx, spaceId, subject, role) +} diff --git a/log/zap/field.go b/log/zap/field.go new file mode 100644 index 0000000000000000000000000000000000000000..e0de88be61059c325821ff68afe0bcbc7b88b6ba --- /dev/null +++ b/log/zap/field.go @@ -0,0 +1,79 @@ +package zap + +import ( + "context" + "fmt" + + "git.perx.ru/perxis/perxis-go/id" + "git.perx.ru/perxis/perxis-go/pkg/auth" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +const ( + unknownObject = "unknown" + unknownCaller = "unknown" +) + +func Category(category string) zapcore.Field { + return zap.String("category", category) +} + +func Component(component string) zapcore.Field { + return zap.String("component", component) +} + +func Event(event string) zapcore.Field { + return zap.String("event", event) +} + +// Object возвращает поле и устанавливает передаваемый аргумент в качестве идентификатора объекта в формате ObjectID. +// Поддерживаемые типы: string, fmt.Stringer. +// Если передан аргумент другого типа, будет произведена попытка привести переданное значение к ObjectID. +func Object(v any) zapcore.Field { + var object = unknownObject + switch value := v.(type) { + case string: + object = value + case fmt.Stringer: + object = value.String() + default: + oid, err := id.FromObject(v) + if err == nil { + object = oid.String() + } + } + return zap.String("object", object) +} + +// Caller возвращает поле и устанавливает передаваемый аргумент в качестве "вызывающего" в формате ObjectID. +// Поддерживаемые типы: string, fmt.Stringer. +// Если передан аргумент другого типа, будет произведена попытка привести переданное значение к ObjectID. +func Caller(v any) zapcore.Field { + var caller = unknownCaller + switch value := v.(type) { + case string: + caller = value + case fmt.Stringer: + caller = value.String() + default: + oid, err := id.FromObject(v) + if err == nil { + caller = oid.String() + } + } + return zap.String("caller", caller) +} + +// CallerFromContext извлекает auth.Principal из контекста и устанавливает его в качестве "вызывающего" в формате ObjectID. +func CallerFromContext(ctx context.Context) zapcore.Field { + return Caller(auth.GetPrincipal(ctx)) +} + +func Attr(attr any) zapcore.Field { + return zap.Any("attr", attr) +} + +func Tags(tags ...string) zapcore.Field { + return zap.Strings("tags", tags) +} diff --git a/pkg/extension/schema.go b/pkg/extension/schema.go index 501de9a247c3ba4240dc50ba641d7e2c56a1d2fc..521ebad6428dbd308228090469f369367e65068d 100644 --- a/pkg/extension/schema.go +++ b/pkg/extension/schema.go @@ -72,7 +72,7 @@ func NewActionsCollection(spaceID, envID string) *collections.Collection { // UI sch.Field.UI.ListView = &field.View{Options: map[string]interface{}{ - "fields": []interface{}{"name", "action", "kind", "updated_at", "updated_by", "state"}, + "fields": []interface{}{"icon", "name", "action", "kind", "updated_at", "updated_by", "state"}, "sort": []interface{}{"name"}, "page_size": float64(50), }}