Skip to content
Snippets Groups Projects
Commit c1584321 authored by Semyon Krestyaninov's avatar Semyon Krestyaninov :dog2:
Browse files

add ToMap and FromMap for item

parent 0b1f2e79
Branches
Tags
No related merge requests found
......@@ -13,6 +13,7 @@ import (
"git.perx.ru/perxis/perxis-go/pkg/schema/localizer"
"git.perx.ru/perxis/perxis-go/pkg/schema/walk"
pb "git.perx.ru/perxis/perxis-go/proto/items"
"github.com/mitchellh/mapstructure"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
)
......@@ -169,29 +170,84 @@ func (i *Item) Clone() *Item {
return &itm
}
func (i *Item) ToMap() map[string]interface{} {
return map[string]interface{}{
"id": i.ID,
"space_id": i.SpaceID,
"env_id": i.EnvID,
"collection_id": i.CollectionID,
"state": i.State,
"created_rev_at": i.CreatedRevAt,
"created_by": i.CreatedBy,
"created_at": i.CreatedAt,
"updated_at": i.UpdatedAt,
"updated_by": i.UpdatedBy,
"revision_id": i.RevisionID,
"revision_description": i.RevisionDescription,
"data": i.Data,
"locale_id": i.LocaleID,
"translations": i.Translations,
"translations_ids": i.TranslationsIDs,
"deleted": i.Deleted,
"hidden": i.Hidden,
"template": i.Template,
"search_score": i.SearchScore,
// ToMap конвертирует переданный Item в map[string]any, кодируя данные согласно схеме.
// Вычисляемые поля удаляются из результата.
func ToMap(item *Item, sch *schema.Schema) (map[string]any, error) {
if item == nil {
return nil, errors.New("item must not be nil")
}
if sch == nil {
return nil, errors.New("schema must not be nil")
}
output := make(map[string]any)
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
TagName: "json",
Result: &output,
})
if err != nil {
return nil, errors.Wrap(err, "failed to create decoder")
}
item, err = item.Encode(context.Background(), sch)
if err != nil {
return nil, errors.Wrap(err, "failed to encode item by schema")
}
err = decoder.Decode(item)
if err != nil {
return nil, errors.Wrap(err, "failed to encode item")
}
// Кодируем системные поля со временем
output["createdRevAt"] = item.CreatedRevAt.Format(time.RFC3339)
output["createdAt"] = item.CreatedAt.Format(time.RFC3339)
output["updatedAt"] = item.UpdatedAt.Format(time.RFC3339)
// Удаляем вычисляемые поля
delete(output, "permissions")
delete(output, "searchScore")
return output, nil
}
// FromMap конвертирует переданный map[string]any в Item, декодируя данные согласно схеме.
// Вычисляемые поля игнорируются при декодировании.
func FromMap(input map[string]any, sch *schema.Schema) (*Item, error) {
if len(input) == 0 {
return nil, errors.New("input map must not be nil")
}
if sch == nil {
return nil, errors.New("schema must not be nil")
}
item := &Item{}
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeHookFunc(time.RFC3339),
TagName: "json",
Result: item,
})
if err != nil {
return nil, errors.Wrap(err, "failed to create decoder")
}
// Удаляем вычисляемые поля
delete(input, "permissions")
delete(input, "searchScore")
err = decoder.Decode(input)
if err != nil {
return nil, errors.Wrap(err, "failed to decode")
}
item, err = item.Decode(context.Background(), sch)
if err != nil {
return nil, errors.Wrap(err, "failed to decode item by schema")
}
return item, nil
}
// SetData устанавливает перевод в нужное поле записи
......
......@@ -506,3 +506,238 @@ func Test_mergeItemData(t *testing.T) {
})
}
}
func TestToMap(t *testing.T) {
sch := schema.New(
"str", field.String(),
"num", field.Number(field.NumberFormatFloat),
"embedded", field.Object(
"now", field.Time(),
),
)
tests := []struct {
name string
input *Item
sch *schema.Schema
want map[string]any
assertErr assert.ErrorAssertionFunc
}{
{
name: "nil item",
input: nil,
sch: nil,
want: nil,
assertErr: assert.Error,
},
{
name: "nil schema",
input: &Item{},
sch: nil,
want: nil,
assertErr: assert.Error,
},
{
name: "simple",
input: &Item{
ID: "item_id",
SpaceID: "space_id",
EnvID: "env_id",
CollectionID: "coll_id",
State: StatePublished,
CreatedRevAt: time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
CreatedBy: "created_by",
CreatedAt: time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
UpdatedAt: time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
UpdatedBy: "updated_by",
Data: map[string]any{
"str": "string",
"num": 2.7,
"embedded": map[string]any{
"now": time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
},
},
LocaleID: "locale_id",
Translations: map[string]map[string]any{
"ru": {
"str": "строка",
"num": 3.14,
},
},
TranslationsIDs: []string{"ru"},
RevisionID: "rev_id",
RevisionDescription: "rev_desc",
Permissions: &Permissions{
Edit: true,
Archive: false,
Publish: true,
SoftDelete: false,
HardDelete: false,
},
SearchScore: 123.0,
Deleted: false,
Hidden: true,
Template: true,
},
sch: sch,
want: map[string]any{
"id": "item_id",
"spaceId": "space_id",
"envId": "env_id",
"collectionId": "coll_id",
"state": StatePublished,
"createdRevAt": "2024-08-12T00:00:00Z",
"createdBy": "created_by",
"createdAt": "2024-08-12T00:00:00Z",
"updatedAt": "2024-08-12T00:00:00Z",
"updatedBy": "updated_by",
"data": map[string]any{
"str": "string",
"num": 2.7,
"embedded": map[string]any{
"now": "2024-08-12T00:00:00Z",
},
},
"localeId": "locale_id",
"translations": map[string]map[string]any{
"ru": {
"str": "строка",
"num": 3.14,
},
},
"translationsIds": []string{"ru"},
"revId": "rev_id",
"revDescription": "rev_desc",
"deleted": false,
"hidden": true,
"template": true,
},
assertErr: assert.NoError,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := ToMap(tc.input, tc.sch)
tc.assertErr(t, err)
assert.Equal(t, tc.want, got)
})
}
}
func TestFromMap(t *testing.T) {
sch := schema.New(
"str", field.String(),
"num", field.Number(field.NumberFormatFloat),
"embedded", field.Object(
"now", field.Time(),
),
)
tests := []struct {
name string
input map[string]any
want *Item
sch *schema.Schema
assertErr assert.ErrorAssertionFunc
}{
{
name: "nil item",
input: nil,
sch: nil,
want: nil,
assertErr: assert.Error,
},
{
name: "empty item",
input: map[string]any{},
sch: nil,
want: nil,
assertErr: assert.Error,
},
{
name: "nil schema",
input: map[string]any{},
sch: nil,
assertErr: assert.Error,
},
{
name: "simple",
input: map[string]any{
"id": "item_id",
"spaceId": "space_id",
"envId": "env_id",
"collectionId": "coll_id",
"state": StatePublished,
"createdRevAt": "2024-08-12T00:00:00Z",
"createdBy": "created_by",
"createdAt": "2024-08-12T00:00:00Z",
"updatedAt": "2024-08-12T00:00:00Z",
"updatedBy": "updated_by",
"data": map[string]any{
"str": "string",
"num": 2.7,
"embedded": map[string]any{
"now": "2024-08-12T00:00:00Z",
},
},
"localeId": "locale_id",
"translations": map[string]map[string]any{
"ru": {
"str": "строка",
"num": 3.14,
},
},
"translationsIds": []string{"ru"},
"revId": "rev_id",
"revDescription": "rev_desc",
"deleted": false,
"hidden": true,
"template": true,
},
sch: sch,
want: &Item{
ID: "item_id",
SpaceID: "space_id",
EnvID: "env_id",
CollectionID: "coll_id",
State: StatePublished,
CreatedRevAt: time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
CreatedBy: "created_by",
CreatedAt: time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
UpdatedAt: time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
UpdatedBy: "updated_by",
Data: map[string]any{
"str": "string",
"num": 2.7,
"embedded": map[string]any{
"now": time.Date(2024, time.August, 12, 00, 0, 0, 0, time.UTC),
},
},
LocaleID: "locale_id",
Translations: map[string]map[string]any{
"ru": {
"str": "строка",
"num": 3.14,
},
},
TranslationsIDs: []string{"ru"},
RevisionID: "rev_id",
RevisionDescription: "rev_desc",
Permissions: nil,
SearchScore: 0.0,
Deleted: false,
Hidden: true,
Template: true,
},
assertErr: assert.NoError,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := FromMap(tc.input, sch)
tc.assertErr(t, err)
assert.Equal(t, tc.want, got)
})
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment