From 805d788f7b00c5eac9df341013533e202d3561c7 Mon Sep 17 00:00:00 2001 From: rot1024 Date: Tue, 1 Feb 2022 22:03:27 +0900 Subject: [PATCH] fix: scene exporter should export layers and tags while maintaining the tree structure (#104) * export layers and tags while maintaining the tree structure * fix * export invislbe layers --- internal/infrastructure/memory/tag.go | 18 +- internal/infrastructure/memory/tag_test.go | 20 +- internal/infrastructure/mongo/mongodoc/tag.go | 2 +- .../infrastructure/mongo/mongodoc/tag_test.go | 8 +- internal/infrastructure/mongo/tag.go | 7 + internal/usecase/interactor/project.go | 4 + internal/usecase/interactor/tag.go | 2 +- internal/usecase/repo/tag.go | 23 + pkg/layer/group.go | 3 + pkg/layer/id.go | 2 + pkg/layer/merged.go | 59 ++- pkg/layer/merged_test.go | 21 + pkg/layer/merging/merged.go | 36 +- pkg/layer/merging/merger.go | 2 +- pkg/layer/merging/merger_test.go | 14 +- pkg/layer/merging/sealed.go | 31 ++ pkg/layer/merging/sealer.go | 54 +- pkg/scene/builder/builder.go | 9 +- pkg/scene/builder/builder_test.go | 478 ++++++++++++------ pkg/scene/builder/encoder.go | 76 ++- pkg/scene/builder/encoder_test.go | 79 ++- pkg/scene/builder/scene.go | 51 +- pkg/tag/group.go | 7 +- pkg/tag/group_builder.go | 11 +- pkg/tag/group_test.go | 6 +- pkg/tag/item.go | 12 + pkg/tag/item_builder.go | 8 + pkg/tag/list.go | 116 ++++- pkg/tag/list_test.go | 91 +++- pkg/tag/loader.go | 46 ++ pkg/tag/map.go | 48 ++ 31 files changed, 1061 insertions(+), 283 deletions(-) create mode 100644 pkg/tag/loader.go create mode 100644 pkg/tag/map.go diff --git a/internal/infrastructure/memory/tag.go b/internal/infrastructure/memory/tag.go index 2cbba397..d3059a02 100644 --- a/internal/infrastructure/memory/tag.go +++ b/internal/infrastructure/memory/tag.go @@ -13,7 +13,7 @@ import ( type Tag struct { lock sync.Mutex - data map[id.TagID]tag.Tag + data tag.Map } func NewTag() repo.Tag { @@ -50,6 +50,13 @@ func (t *Tag) FindByIDs(ctx context.Context, tids []id.TagID, ids []id.SceneID) return res, nil } +func (t *Tag) FindByScene(ctx context.Context, sceneID id.SceneID) ([]*tag.Tag, error) { + t.lock.Lock() + defer t.lock.Unlock() + + return t.data.All().FilterByScene(sceneID).Refs(), nil +} + func (t *Tag) FindItemByID(ctx context.Context, tagID id.TagID, ids []id.SceneID) (*tag.Item, error) { t.lock.Lock() defer t.lock.Unlock() @@ -116,14 +123,7 @@ func (t *Tag) FindRootsByScene(ctx context.Context, sceneID id.SceneID) ([]*tag. t.lock.Lock() defer t.lock.Unlock() - var res []*tag.Tag - for _, tag := range t.data { - tag := tag - if tag.Scene() == sceneID { - res = append(res, &tag) - } - } - return res, nil + return t.data.All().FilterByScene(sceneID).Roots().Refs(), nil } func (t *Tag) Save(ctx context.Context, tag tag.Tag) error { diff --git a/internal/infrastructure/memory/tag_test.go b/internal/infrastructure/memory/tag_test.go index c6d220bc..155c3472 100644 --- a/internal/infrastructure/memory/tag_test.go +++ b/internal/infrastructure/memory/tag_test.go @@ -4,11 +4,9 @@ import ( "context" "testing" - "github.com/reearth/reearth-backend/pkg/rerror" - "github.com/reearth/reearth-backend/pkg/id" + "github.com/reearth/reearth-backend/pkg/rerror" "github.com/reearth/reearth-backend/pkg/tag" - "github.com/stretchr/testify/assert" ) @@ -41,7 +39,7 @@ func TestTag_FindByIDs(t *testing.T) { sid2 := id.NewSceneID() sl := []id.SceneID{sid} t1, _ := tag.NewItem().NewID().Scene(sid).Label("item").Build() - tl := tag.NewListFromTags([]id.TagID{t1.ID()}) + tl := tag.IDListFrom([]id.TagID{t1.ID()}) t2, _ := tag.NewGroup().NewID().Scene(sid).Label("group").Tags(tl).Build() t3, _ := tag.NewItem().NewID().Scene(sid2).Label("item2").Build() tti := tag.Tag(t1) @@ -64,7 +62,7 @@ func TestTag_FindRootsByScene(t *testing.T) { sid := id.NewSceneID() sid2 := id.NewSceneID() t1, _ := tag.NewItem().NewID().Scene(sid).Label("item").Build() - tl := tag.NewListFromTags([]id.TagID{t1.ID()}) + tl := tag.IDListFrom([]id.TagID{t1.ID()}) t2, _ := tag.NewGroup().NewID().Scene(sid).Label("group").Tags(tl).Build() t3, _ := tag.NewItem().NewID().Scene(sid2).Label("item2").Build() tti := tag.Tag(t1) @@ -87,7 +85,7 @@ func TestTag_FindGroupByID(t *testing.T) { sid := id.NewSceneID() sl := []id.SceneID{sid} t1, _ := tag.NewItem().NewID().Scene(sid).Label("item").Build() - tl := tag.NewListFromTags([]id.TagID{t1.ID()}) + tl := tag.IDListFrom([]id.TagID{t1.ID()}) t2, _ := tag.NewGroup().NewID().Scene(sid).Label("group").Tags(tl).Build() tti := tag.Tag(t1) ttg := tag.Tag(t2) @@ -110,7 +108,7 @@ func TestTag_FindItemByID(t *testing.T) { sid := id.NewSceneID() sl := []id.SceneID{sid} t1, _ := tag.NewItem().NewID().Scene(sid).Label("item").Build() - tl := tag.NewListFromTags([]id.TagID{t1.ID()}) + tl := tag.IDListFrom([]id.TagID{t1.ID()}) t2, _ := tag.NewGroup().NewID().Scene(sid).Label("group").Tags(tl).Build() tti := tag.Tag(t1) ttg := tag.Tag(t2) @@ -208,7 +206,7 @@ func TestTag_Remove(t *testing.T) { ctx := context.Background() sid := id.NewSceneID() t1, _ := tag.NewItem().NewID().Scene(sid).Label("item").Build() - tl := tag.NewListFromTags([]id.TagID{t1.ID()}) + tl := tag.IDListFrom([]id.TagID{t1.ID()}) t2, _ := tag.NewGroup().NewID().Scene(sid).Label("group").Tags(tl).Build() tti := tag.Tag(t1) ttg := tag.Tag(t2) @@ -228,7 +226,7 @@ func TestTag_RemoveAll(t *testing.T) { ctx := context.Background() sid := id.NewSceneID() t1, _ := tag.NewItem().NewID().Scene(sid).Label("item").Build() - tl := tag.NewListFromTags([]id.TagID{t1.ID()}) + tl := tag.IDListFrom([]id.TagID{t1.ID()}) t2, _ := tag.NewItem().NewID().Scene(sid).Label("item").Build() t3, _ := tag.NewGroup().NewID().Scene(sid).Label("group").Tags(tl).Build() tti := tag.Tag(t1) @@ -252,7 +250,7 @@ func TestTag_RemoveByScene(t *testing.T) { sid := id.NewSceneID() sid2 := id.NewSceneID() t1, _ := tag.NewItem().NewID().Scene(sid).Label("item").Build() - tl := tag.NewListFromTags([]id.TagID{t1.ID()}) + tl := tag.IDListFrom([]id.TagID{t1.ID()}) t2, _ := tag.NewItem().NewID().Scene(sid2).Label("item").Build() t3, _ := tag.NewGroup().NewID().Scene(sid).Label("group").Tags(tl).Build() tti := tag.Tag(t1) @@ -276,7 +274,7 @@ func TestTag_FindGroupByItem(t *testing.T) { sid := id.NewSceneID() sl := []id.SceneID{sid} t1, _ := tag.NewItem().NewID().Scene(sid).Label("item").Build() - tl := tag.NewListFromTags([]id.TagID{t1.ID()}) + tl := tag.IDListFrom([]id.TagID{t1.ID()}) t2, _ := tag.NewGroup().NewID().Scene(sid).Label("group").Tags(tl).Build() tti := tag.Tag(t1) ttg := tag.Tag(t2) diff --git a/internal/infrastructure/mongo/mongodoc/tag.go b/internal/infrastructure/mongo/mongodoc/tag.go index 3c270efb..68c394b6 100644 --- a/internal/infrastructure/mongo/mongodoc/tag.go +++ b/internal/infrastructure/mongo/mongodoc/tag.go @@ -177,6 +177,6 @@ func (d *TagDocument) ModelGroup() (*tag.Group, error) { ID(tid). Label(d.Label). Scene(sid). - Tags(tag.NewListFromTags(ids)). + Tags(tag.IDListFrom(ids)). Build() } diff --git a/internal/infrastructure/mongo/mongodoc/tag_test.go b/internal/infrastructure/mongo/mongodoc/tag_test.go index b4cd6561..c9934ef6 100644 --- a/internal/infrastructure/mongo/mongodoc/tag_test.go +++ b/internal/infrastructure/mongo/mongodoc/tag_test.go @@ -25,7 +25,7 @@ func TestNewTag(t *testing.T) { tg, _ := tag.NewGroup(). NewID(). Label("group"). - Tags(tag.NewListFromTags([]id.TagID{ti.ID()})). + Tags(tag.IDListFrom([]id.TagID{ti.ID()})). Scene(sid). Build() type args struct { @@ -93,7 +93,7 @@ func TestNewTags(t *testing.T) { tg, _ := tag.NewGroup(). NewID(). Label("group"). - Tags(tag.NewListFromTags([]id.TagID{ti.ID()})). + Tags(tag.IDListFrom([]id.TagID{ti.ID()})). Scene(sid). Build() tgi := tag.Tag(tg) @@ -252,7 +252,7 @@ func TestTagDocument_Model(t *testing.T) { tg, _ := tag.NewGroup(). NewID(). Label("group"). - Tags(tag.NewListFromTags([]id.TagID{ti.ID()})). + Tags(tag.IDListFrom([]id.TagID{ti.ID()})). Scene(sid). Build() type fields struct { @@ -341,7 +341,7 @@ func TestTagDocument_ModelGroup(t *testing.T) { tg, _ := tag.NewGroup(). NewID(). Label("group"). - Tags(tag.NewListFromTags([]id.TagID{ti.ID()})). + Tags(tag.IDListFrom([]id.TagID{ti.ID()})). Scene(sid). Build() type fields struct { diff --git a/internal/infrastructure/mongo/tag.go b/internal/infrastructure/mongo/tag.go index 5aed97d8..ab82b26c 100644 --- a/internal/infrastructure/mongo/tag.go +++ b/internal/infrastructure/mongo/tag.go @@ -51,6 +51,13 @@ func (r *tagRepo) FindByIDs(ctx context.Context, ids []id.TagID, f []id.SceneID) return filterTags(ids, res), nil } +func (r *tagRepo) FindByScene(ctx context.Context, id id.SceneID) ([]*tag.Tag, error) { + filter := bson.M{ + "scene": id.String(), + } + return r.find(ctx, nil, filter) +} + func (r *tagRepo) FindItemByID(ctx context.Context, id id.TagID, f []id.SceneID) (*tag.Item, error) { filter := r.sceneFilter(bson.D{ {Key: "id", Value: id.String()}, diff --git a/internal/usecase/interactor/project.go b/internal/usecase/interactor/project.go index 889ad4a3..0bc54e74 100644 --- a/internal/usecase/interactor/project.go +++ b/internal/usecase/interactor/project.go @@ -29,6 +29,7 @@ type Project struct { layerRepo repo.Layer datasetRepo repo.Dataset datasetSchemaRepo repo.DatasetSchema + tagRepo repo.Tag transaction repo.Transaction file gateway.File } @@ -46,6 +47,7 @@ func NewProject(r *repo.Container, gr *gateway.Container) interfaces.Project { layerRepo: r.Layer, datasetRepo: r.Dataset, datasetSchemaRepo: r.DatasetSchema, + tagRepo: r.Tag, transaction: r.Transaction, file: gr.File, } @@ -315,6 +317,8 @@ func (i *Project) Publish(ctx context.Context, params interfaces.PublishProjectP repo.LayerLoaderFrom(i.layerRepo, scenes), repo.PropertyLoaderFrom(i.propertyRepo, scenes), repo.DatasetGraphLoaderFrom(i.datasetRepo, scenes), + repo.TagLoaderFrom(i.tagRepo, scenes), + repo.TagSceneLoaderFrom(i.tagRepo, scenes), ).BuildScene(ctx, w, s, time.Now()) }() diff --git a/internal/usecase/interactor/tag.go b/internal/usecase/interactor/tag.go index 5beb030b..52678dee 100644 --- a/internal/usecase/interactor/tag.go +++ b/internal/usecase/interactor/tag.go @@ -103,7 +103,7 @@ func (i *Tag) CreateGroup(ctx context.Context, inp interfaces.CreateTagGroupPara return nil, interfaces.ErrOperationDenied } - list := tag.NewListFromTags(inp.Tags) + list := tag.IDListFrom(inp.Tags) group, err := tag.NewGroup(). NewID(). Label(inp.Label). diff --git a/internal/usecase/repo/tag.go b/internal/usecase/repo/tag.go index 456642f7..b3c8bcda 100644 --- a/internal/usecase/repo/tag.go +++ b/internal/usecase/repo/tag.go @@ -10,6 +10,7 @@ import ( type Tag interface { FindByID(context.Context, id.TagID, []id.SceneID) (tag.Tag, error) FindByIDs(context.Context, []id.TagID, []id.SceneID) ([]*tag.Tag, error) + FindByScene(context.Context, id.SceneID) ([]*tag.Tag, error) FindItemByID(context.Context, id.TagID, []id.SceneID) (*tag.Item, error) FindItemByIDs(context.Context, []id.TagID, []id.SceneID) ([]*tag.Item, error) FindGroupByID(context.Context, id.TagID, []id.SceneID) (*tag.Group, error) @@ -22,3 +23,25 @@ type Tag interface { RemoveAll(context.Context, []id.TagID) error RemoveByScene(context.Context, id.SceneID) error } + +func TagLoaderFrom(r Tag, scenes []id.SceneID) tag.Loader { + return func(ctx context.Context, ids ...id.TagID) ([]*tag.Tag, error) { + return r.FindByIDs(ctx, ids, scenes) + } +} + +func TagSceneLoaderFrom(r Tag, scenes []id.SceneID) tag.SceneLoader { + return func(ctx context.Context, id id.SceneID) ([]*tag.Tag, error) { + found := false + for _, s := range scenes { + if id == s { + found = true + break + } + } + if !found { + return nil, nil + } + return r.FindByScene(ctx, id) + } +} diff --git a/pkg/layer/group.go b/pkg/layer/group.go index 61a0d887..8a7ed345 100644 --- a/pkg/layer/group.go +++ b/pkg/layer/group.go @@ -180,6 +180,9 @@ func (l *Group) ValidateProperties(pm property.Map) error { } func (l *Group) Tags() *TagList { + if l == nil { + return nil + } if l.layerBase.tags == nil { l.layerBase.tags = NewTagList(nil) } diff --git a/pkg/layer/id.go b/pkg/layer/id.go index b34b0271..eb9b6dad 100644 --- a/pkg/layer/id.go +++ b/pkg/layer/id.go @@ -60,10 +60,12 @@ var DatasetSchemaIDFromRefID = id.DatasetSchemaIDFromRefID type IDSet = id.LayerIDSet type InfoboxFIeldIDSet = id.InfoboxFieldIDSet type DatasetIDSet = id.DatasetIDSet +type TagIDSet = id.TagIDSet var NewIDSet = id.NewLayerIDSet var NewInfoboxFIeldIDSet = id.NewInfoboxFieldIDSet var NewDatasetIDSet = id.NewDatasetIDSet +var NewTagIDSet = id.NewTagIDSet var OfficialPluginID = id.OfficialPluginID var ErrInvalidID = id.ErrInvalidID diff --git a/pkg/layer/merged.go b/pkg/layer/merged.go index dfbeb2e8..ed7c98d2 100644 --- a/pkg/layer/merged.go +++ b/pkg/layer/merged.go @@ -14,6 +14,14 @@ type Merged struct { Infobox *MergedInfobox PluginID *PluginID ExtensionID *PluginExtensionID + IsVisible bool + Tags []MergedTag +} + +// MergedTag represents a merged tag from two layers +type MergedTag struct { + ID TagID + Tags []MergedTag } // MergedInfobox represents a merged info box from two layers @@ -48,8 +56,37 @@ func Merge(o Layer, p *Group) *Merged { Parent: p.Property(), LinkedDataset: ToLayerItem(o).LinkedDataset(), }, - Infobox: MergeInfobox(o.Infobox(), p.Infobox(), ToLayerItem(o).LinkedDataset()), + IsVisible: o.IsVisible(), + Tags: MergeTags(o.Tags(), p.Tags()), + Infobox: MergeInfobox(o.Infobox(), p.Infobox(), ToLayerItem(o).LinkedDataset()), + } +} + +// MergeInfobox merges two tag lists +func MergeTags(o, _p *TagList) []MergedTag { + // Currently parent tags are ignored + tags := o.Tags() + if len(tags) == 0 { + return nil + } + res := make([]MergedTag, 0, len(tags)) + for _, t := range tags { + tags := TagGroupFrom(t).Children() + + var tags2 []MergedTag + if len(tags) > 0 { + tags2 = make([]MergedTag, 0, len(tags)) + for _, t := range tags { + tags2 = append(tags2, MergedTag{ID: t.ID()}) + } + } + + res = append(res, MergedTag{ + ID: t.ID(), + Tags: tags2, + }) } + return res } // MergeInfobox merges two infoboxes @@ -149,3 +186,23 @@ func (m *Merged) Properties() []PropertyID { } return result } + +func (m *Merged) AllTags() (res []MergedTag) { + if m == nil { + return nil + } + for _, t := range m.Tags { + res = append(res, append([]MergedTag{t}, t.Tags...)...) + } + return res +} + +func (m *Merged) AllTagIDs() (res []TagID) { + if m == nil { + return nil + } + for _, t := range m.AllTags() { + res = append(res, t.ID) + } + return res +} diff --git a/pkg/layer/merged_test.go b/pkg/layer/merged_test.go index cdd2d89a..e10ba585 100644 --- a/pkg/layer/merged_test.go +++ b/pkg/layer/merged_test.go @@ -13,6 +13,9 @@ func TestMerge(t *testing.T) { p := MustPluginID("xxx~1.1.1") e := PluginExtensionID("foo") + t1 := NewTagID() + t2 := NewTagID() + t3 := NewTagID() itemProperty := NewPropertyID() groupProperty := NewPropertyID() ib1pr := NewPropertyID() @@ -32,6 +35,7 @@ func TestMerge(t *testing.T) { Plugin(&p). Extension(&e). Property(&itemProperty). + IsVisible(false). MustBuild() // no-infobox itemLayer2 := NewItem(). @@ -41,6 +45,7 @@ func TestMerge(t *testing.T) { Extension(&e). Property(&itemProperty). LinkedDataset(&dataset1). + Tags(NewTagList([]Tag{NewTagGroup(t1, []*TagItem{NewTagItem(t2)}), NewTagItem(t3)})). MustBuild() // infobox itemLayer3 := NewItem(). @@ -69,6 +74,7 @@ func TestMerge(t *testing.T) { Plugin(&p). Extension(&e). Property(&groupProperty). + Tags(NewTagList([]Tag{NewTagGroup(t1, []*TagItem{NewTagItem(t2)}), NewTagItem(t3)})). MustBuild() // infobox groupLayer2 := NewGroup(). @@ -108,6 +114,7 @@ func TestMerge(t *testing.T) { Scene: scene, PluginID: &p, ExtensionID: &e, + IsVisible: false, Property: &property.MergedMetadata{ Original: &itemProperty, Parent: nil, @@ -125,6 +132,7 @@ func TestMerge(t *testing.T) { Scene: scene, PluginID: &p, ExtensionID: &e, + IsVisible: true, Property: &property.MergedMetadata{ Original: &itemProperty, Parent: nil, @@ -171,6 +179,11 @@ func TestMerge(t *testing.T) { Scene: scene, PluginID: &p, ExtensionID: &e, + IsVisible: true, + Tags: []MergedTag{ + {ID: t1, Tags: []MergedTag{{ID: t2}}}, + {ID: t3}, + }, Property: &property.MergedMetadata{ Original: &itemProperty, Parent: &groupProperty, @@ -188,6 +201,7 @@ func TestMerge(t *testing.T) { Scene: scene, PluginID: &p, ExtensionID: &e, + IsVisible: true, Property: &property.MergedMetadata{ Original: &itemProperty, Parent: &groupProperty, @@ -234,6 +248,11 @@ func TestMerge(t *testing.T) { Scene: scene, PluginID: &p, ExtensionID: &e, + IsVisible: true, + Tags: []MergedTag{ + {ID: t1, Tags: []MergedTag{{ID: t2}}}, + {ID: t3}, + }, Property: &property.MergedMetadata{ Original: &itemProperty, Parent: &groupProperty, @@ -280,6 +299,7 @@ func TestMerge(t *testing.T) { Scene: scene, PluginID: &p, ExtensionID: &e, + IsVisible: true, Property: &property.MergedMetadata{ Original: &itemProperty, Parent: &groupProperty, @@ -326,6 +346,7 @@ func TestMerge(t *testing.T) { Scene: scene, PluginID: &p, ExtensionID: &e, + IsVisible: true, Property: &property.MergedMetadata{ Original: &itemProperty, Parent: &groupProperty, diff --git a/pkg/layer/merging/merged.go b/pkg/layer/merging/merged.go index 3111b172..2ba6e0a6 100644 --- a/pkg/layer/merging/merged.go +++ b/pkg/layer/merging/merged.go @@ -13,6 +13,7 @@ var ( type MergedLayer interface { Common() *MergedLayerCommon AllDatasets() []layer.DatasetID + AllTags() []layer.TagID } type MergedLayerGroup struct { @@ -59,6 +60,10 @@ func (l *MergedLayerCommon) Datasets() []layer.DatasetID { return l.datasetIDSet().All() } +func (l *MergedLayerCommon) Tags() []layer.TagID { + return l.tagIDSet().All() +} + func (l *MergedLayerCommon) datasetIDSet() *layer.DatasetIDSet { if l == nil { return nil @@ -72,6 +77,15 @@ func (l *MergedLayerCommon) datasetIDSet() *layer.DatasetIDSet { return res } +func (l *MergedLayerCommon) tagIDSet() *layer.TagIDSet { + if l == nil { + return nil + } + res := layer.NewTagIDSet() + res.Add(l.Merged.AllTagIDs()...) + return res +} + func (l *MergedLayerItem) AllDatasets() []layer.DatasetID { if l == nil { return nil @@ -79,11 +93,14 @@ func (l *MergedLayerItem) AllDatasets() []layer.DatasetID { return l.Datasets() } -func (l *MergedLayerGroup) AllDatasets() []layer.DatasetID { - return l.allDatasetIDSet().All() +func (l *MergedLayerItem) AllTags() []layer.TagID { + if l == nil { + return nil + } + return l.Tags() } -func (l *MergedLayerGroup) allDatasetIDSet() *layer.DatasetIDSet { +func (l *MergedLayerGroup) AllDatasets() []layer.DatasetID { if l == nil { return nil } @@ -91,5 +108,16 @@ func (l *MergedLayerGroup) allDatasetIDSet() *layer.DatasetIDSet { for _, l := range l.Children { d.Add(l.AllDatasets()...) } - return d + return d.All() +} + +func (l *MergedLayerGroup) AllTags() []layer.TagID { + if l == nil { + return nil + } + d := l.tagIDSet() + for _, l := range l.Children { + d.Add(l.AllTags()...) + } + return d.All() } diff --git a/pkg/layer/merging/merger.go b/pkg/layer/merging/merger.go index 41a9a0bd..7c1153bf 100644 --- a/pkg/layer/merging/merger.go +++ b/pkg/layer/merging/merger.go @@ -37,7 +37,7 @@ func (m *Merger) MergeLayer(ctx context.Context, l layer.Layer, parent *layer.Gr children := make([]MergedLayer, 0, len(layers)) for _, c := range layers { - if c == nil || !(*c).IsVisible() { + if c == nil { continue } ml, err := m.MergeLayer(ctx, *c, lg) diff --git a/pkg/layer/merging/merger_test.go b/pkg/layer/merging/merger_test.go index 52c81b0b..573e72fa 100644 --- a/pkg/layer/merging/merger_test.go +++ b/pkg/layer/merging/merger_test.go @@ -42,6 +42,7 @@ func TestMergeLayer(t *testing.T) { Property(&itemProperty). LinkedDataset(&dataset1). Infobox(layer.NewInfobox(nil, ib1pr)). + IsVisible(false). MustBuild(), layer.NewGroup(). ID(l2). @@ -51,6 +52,7 @@ func TestMergeLayer(t *testing.T) { layer.NewInfoboxField().ID(l1if1).Plugin(p).Extension(e).Property(fpr).MustBuild(), }, ib2pr)). Layers(layer.NewIDList([]layer.ID{l1})). + IsVisible(false). MustBuild(), }) @@ -80,8 +82,9 @@ func TestMergeLayer(t *testing.T) { expected := &MergedLayerGroup{ MergedLayerCommon: MergedLayerCommon{ Merged: layer.Merged{ - Original: l2, - Scene: scene, + Original: l2, + Scene: scene, + IsVisible: false, Property: &property.MergedMetadata{ Original: &groupProperty, }, @@ -112,9 +115,10 @@ func TestMergeLayer(t *testing.T) { &MergedLayerItem{ MergedLayerCommon{ Merged: layer.Merged{ - Original: l1, - Parent: &l2, - Scene: scene, + Original: l1, + Parent: &l2, + Scene: scene, + IsVisible: false, Property: &property.MergedMetadata{ Original: &itemProperty, Parent: &groupProperty, diff --git a/pkg/layer/merging/sealed.go b/pkg/layer/merging/sealed.go index 2ca70a01..4a6dbf66 100644 --- a/pkg/layer/merging/sealed.go +++ b/pkg/layer/merging/sealed.go @@ -13,6 +13,8 @@ var ( type SealedLayer interface { Common() *SealedLayerCommon Flatten() []*SealedLayerItem + Group() *SealedLayerGroup + Item() *SealedLayerItem } type SealedLayerGroup struct { @@ -28,6 +30,7 @@ type SealedLayerCommon struct { layer.Merged Property *property.Sealed Infobox *SealedInfobox + Tags []SealedTag } type SealedInfobox struct { @@ -41,6 +44,12 @@ type SealedInfoboxField struct { Property *property.Sealed } +type SealedTag struct { + ID layer.TagID + Label string + Tags []SealedTag +} + func (l *SealedLayerGroup) Common() *SealedLayerCommon { if l == nil { return nil @@ -59,6 +68,17 @@ func (l *SealedLayerGroup) Flatten() []*SealedLayerItem { return layers } +func (l *SealedLayerGroup) Item() *SealedLayerItem { + return nil +} + +func (l *SealedLayerGroup) Group() *SealedLayerGroup { + if l == nil { + return nil + } + return l +} + func (l *SealedLayerItem) Common() *SealedLayerCommon { if l == nil { return nil @@ -72,3 +92,14 @@ func (l *SealedLayerItem) Flatten() []*SealedLayerItem { } return []*SealedLayerItem{l} } + +func (l *SealedLayerItem) Item() *SealedLayerItem { + if l == nil { + return nil + } + return l +} + +func (*SealedLayerItem) Group() *SealedLayerGroup { + return nil +} diff --git a/pkg/layer/merging/sealer.go b/pkg/layer/merging/sealer.go index 94c0948c..d4a6bacf 100644 --- a/pkg/layer/merging/sealer.go +++ b/pkg/layer/merging/sealer.go @@ -4,39 +4,52 @@ import ( "context" "github.com/reearth/reearth-backend/pkg/dataset" + "github.com/reearth/reearth-backend/pkg/layer" "github.com/reearth/reearth-backend/pkg/property" + "github.com/reearth/reearth-backend/pkg/tag" ) type Sealer struct { DatasetGraphLoader dataset.GraphLoader + TagLoader tag.Loader } func (s *Sealer) Seal(ctx context.Context, m MergedLayer) (SealedLayer, error) { if s == nil || m == nil { return nil, nil } - return s.sealLayer(ctx, m) + + var tagMap tag.Map + if tags := m.AllTags(); len(tags) > 0 { + tags2, err := s.TagLoader(ctx, tags...) + if err != nil { + return nil, err + } + tagMap = tag.MapFromRefList(tags2) + } + + return s.sealLayer(ctx, m, tagMap) } -func (s *Sealer) sealLayer(ctx context.Context, m MergedLayer) (SealedLayer, error) { +func (s *Sealer) sealLayer(ctx context.Context, m MergedLayer, tagMap tag.Map) (SealedLayer, error) { if s == nil || m == nil { return nil, nil } if g, ok := m.(*MergedLayerGroup); ok { - return s.sealLayerGroup(ctx, g) + return s.sealLayerGroup(ctx, g, tagMap) } if i, ok := m.(*MergedLayerItem); ok { - return s.sealLayerItem(ctx, i) + return s.sealLayerItem(ctx, i, tagMap) } return nil, nil } -func (s *Sealer) sealLayerGroup(ctx context.Context, m *MergedLayerGroup) (*SealedLayerGroup, error) { +func (s *Sealer) sealLayerGroup(ctx context.Context, m *MergedLayerGroup, tagMap tag.Map) (*SealedLayerGroup, error) { if s == nil || m == nil { return nil, nil } - c, err := s.sealLayerCommon(ctx, &m.MergedLayerCommon) + c, err := s.sealLayerCommon(ctx, &m.MergedLayerCommon, tagMap) if err != nil { return nil, err } @@ -46,7 +59,7 @@ func (s *Sealer) sealLayerGroup(ctx context.Context, m *MergedLayerGroup) (*Seal children := make([]SealedLayer, 0, len(m.Children)) for _, c := range m.Children { - s, err := s.sealLayer(ctx, c) + s, err := s.sealLayer(ctx, c, tagMap) if err != nil { return nil, err } @@ -59,11 +72,11 @@ func (s *Sealer) sealLayerGroup(ctx context.Context, m *MergedLayerGroup) (*Seal }, nil } -func (s *Sealer) sealLayerItem(ctx context.Context, m *MergedLayerItem) (*SealedLayerItem, error) { +func (s *Sealer) sealLayerItem(ctx context.Context, m *MergedLayerItem, tagMap tag.Map) (*SealedLayerItem, error) { if s == nil || m == nil { return nil, nil } - c, err := s.sealLayerCommon(ctx, &m.MergedLayerCommon) + c, err := s.sealLayerCommon(ctx, &m.MergedLayerCommon, tagMap) if err != nil { return nil, err } @@ -75,7 +88,7 @@ func (s *Sealer) sealLayerItem(ctx context.Context, m *MergedLayerItem) (*Sealed }, nil } -func (s *Sealer) sealLayerCommon(ctx context.Context, m *MergedLayerCommon) (*SealedLayerCommon, error) { +func (s *Sealer) sealLayerCommon(ctx context.Context, m *MergedLayerCommon, tagMap tag.Map) (*SealedLayerCommon, error) { if s == nil || m == nil { return nil, nil } @@ -87,10 +100,12 @@ func (s *Sealer) sealLayerCommon(ctx context.Context, m *MergedLayerCommon) (*Se if err != nil { return nil, err } + tags := s.sealTags(m.Merged.Tags, tagMap) return &SealedLayerCommon{ Merged: m.Merged, Property: p, Infobox: ib, + Tags: tags, }, nil } @@ -137,3 +152,22 @@ func (s *Sealer) sealProperty(ctx context.Context, m *property.Merged) (*propert } return property.Seal(ctx, m, s.DatasetGraphLoader) } + +func (s *Sealer) sealTags(m []layer.MergedTag, tagMap tag.Map) []SealedTag { + if len(m) == 0 { + return nil + } + res := make([]SealedTag, 0, len(m)) + for _, t := range m { + tt := SealedTag{ + ID: t.ID, + Tags: s.sealTags(t.Tags, tagMap), + Label: "", + } + if ttt, ok := tagMap[t.ID]; ok { + tt.Label = ttt.Label() + } + res = append(res, tt) + } + return res +} diff --git a/pkg/scene/builder/builder.go b/pkg/scene/builder/builder.go index 2d6eff88..b3123c57 100644 --- a/pkg/scene/builder/builder.go +++ b/pkg/scene/builder/builder.go @@ -12,6 +12,7 @@ import ( "github.com/reearth/reearth-backend/pkg/layer/merging" "github.com/reearth/reearth-backend/pkg/property" "github.com/reearth/reearth-backend/pkg/scene" + "github.com/reearth/reearth-backend/pkg/tag" ) const ( @@ -21,14 +22,16 @@ const ( type Builder struct { ploader property.Loader + tloader tag.SceneLoader exporter *encoding.Exporter encoder *encoder } -func New(ll layer.Loader, pl property.Loader, dl dataset.GraphLoader) *Builder { +func New(ll layer.Loader, pl property.Loader, dl dataset.GraphLoader, tl tag.Loader, tsl tag.SceneLoader) *Builder { e := &encoder{} return &Builder{ ploader: pl, + tloader: tsl, encoder: e, exporter: &encoding.Exporter{ Merger: &merging.Merger{ @@ -37,6 +40,7 @@ func New(ll layer.Loader, pl property.Loader, dl dataset.GraphLoader) *Builder { }, Sealer: &merging.Sealer{ DatasetGraphLoader: dl, + TagLoader: tl, }, Encoder: e, }, @@ -73,6 +77,5 @@ func (b *Builder) buildScene(ctx context.Context, s *scene.Scene, publishedAt ti } layers := b.encoder.Result() - res := b.scene(ctx, s, publishedAt, layers, p) - return res, nil + return b.scene(ctx, s, publishedAt, layers, p) } diff --git a/pkg/scene/builder/builder_test.go b/pkg/scene/builder/builder_test.go index c84c9d9b..40bc388f 100644 --- a/pkg/scene/builder/builder_test.go +++ b/pkg/scene/builder/builder_test.go @@ -9,10 +9,13 @@ import ( "github.com/reearth/reearth-backend/pkg/layer" "github.com/reearth/reearth-backend/pkg/property" "github.com/reearth/reearth-backend/pkg/scene" + "github.com/reearth/reearth-backend/pkg/tag" "github.com/stretchr/testify/assert" ) func TestSceneBuilder(t *testing.T) { + publishedAt := time.Date(2019, time.August, 15, 0, 0, 0, 0, time.Local) + // ids sceneID := scene.NewID() scenePropertyID := property.NewID() @@ -66,6 +69,21 @@ func TestSceneBuilder(t *testing.T) { ), }).Scene(sceneID).Schema(dss3id).Source("ds3").MustBuild() + // tags + tag1 := tag.NewItem().NewID().Label("hoge").Scene(sceneID).MustBuild() + tag2 := tag.NewItem().NewID().Label("foo").Scene(sceneID).MustBuild() + tag3 := tag.NewItem().NewID().Label("unused").Scene(sceneID).MustBuild() + tag4 := tag.NewGroup().NewID().Label("bar").Scene(sceneID).Tags(tag.IDListFrom(([]tag.ID{ + tag1.ID(), tag2.ID(), tag3.ID(), + }))).MustBuild() + tag5 := tag.NewItem().NewID().Label("dummy").Scene(scene.NewID()).MustBuild() // dummy + tags := tag.List{tag1, tag2, tag3, tag4, tag5} + + // layer tags + ltag1 := layer.NewTagItem(tag1.ID()) + ltag2 := layer.NewTagItem(tag2.ID()) + ltag3 := layer.NewTagGroup(tag4.ID(), []*layer.TagItem{ltag2}) + // layer1: normal layer item layer1p := property.New(). NewID(). @@ -89,6 +107,7 @@ func TestSceneBuilder(t *testing.T) { Plugin(&pluginID). Extension(&pluginExtension1ID). Property(layer1p.IDRef()). + Tags(layer.NewTagList([]layer.Tag{ltag1, ltag3})). MustBuild() // layer2: normal layer group @@ -114,6 +133,7 @@ func TestSceneBuilder(t *testing.T) { Plugin(&pluginID). Extension(&pluginExtension1ID). Property(layer21p.IDRef()). + Tags(layer.NewTagList([]layer.Tag{ltag2})). MustBuild() layer2p := property.New(). NewID(). @@ -131,7 +151,12 @@ func TestSceneBuilder(t *testing.T) { }).MustBuild(), }). MustBuild() - layer2ibf1 := layer.NewInfoboxField().NewID().Plugin(pluginID).Extension(pluginExtension1ID).Property(layer2p.ID()).MustBuild() + layer2ibf1 := layer.NewInfoboxField(). + NewID(). + Plugin(pluginID). + Extension(pluginExtension1ID). + Property(layer2p.ID()). + MustBuild() layer2ib := layer.NewInfobox([]*layer.InfoboxField{ layer2ibf1, }, scenePropertyID) @@ -143,6 +168,7 @@ func TestSceneBuilder(t *testing.T) { Property(layer2p.IDRef()). Infobox(layer2ib). Layers(layer.NewIDList([]layer.ID{layer21.ID()})). + Tags(layer.NewTagList([]layer.Tag{ltag1, ltag3})). MustBuild() // layer3: full-linked layer item with infobox @@ -227,7 +253,12 @@ func TestSceneBuilder(t *testing.T) { }).MustBuild(), }). MustBuild() - layer4ibf1 := layer.NewInfoboxField().NewID().Plugin(pluginID).Extension(pluginExtension1ID).Property(layer4p.ID()).MustBuild() + layer4ibf1 := layer.NewInfoboxField(). + NewID(). + Plugin(pluginID). + Extension(pluginExtension1ID). + Property(layer4p.ID()). + MustBuild() layer4ib := layer.NewInfobox([]*layer.InfoboxField{ layer4ibf1, }, scenePropertyID) @@ -363,14 +394,14 @@ func TestSceneBuilder(t *testing.T) { pluginExtension1ID, scenePropertyID, false, - true) + false) sceneWidget2 := scene.MustNewWidget( sceneWidgetID2, pluginID, pluginExtension2ID, scenePropertyID, true, - false) + true) scenePlugin1 := scene.NewPlugin(pluginID, &scenePropertyID) assert.Equal(t, sceneWidgetID1, sceneWidget1.ID()) @@ -416,166 +447,327 @@ func TestSceneBuilder(t *testing.T) { layer51p, layer6p, }) + tloader := tag.LoaderFrom(tags) + tsloader := tag.SceneLoaderFrom(tags) - // exec - sb := New(lloader, ploader, dloader) - publishedAt := time.Date(2019, time.August, 15, 0, 0, 0, 0, time.Local) - result, err := sb.buildScene(context.Background(), scene, publishedAt) - - // general - assert.NoError(t, err) - assert.Equal(t, sceneID.String(), result.ID) - assert.Equal(t, version, result.SchemaVersion) - assert.Equal(t, publishedAt, result.PublishedAt) - - // property - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "hogehoge", + expectedLayer1 := &layerJSON{ + ID: layer1.ID().String(), + PluginID: layer1.Plugin().StringRef(), + ExtensionID: layer1.Extension().StringRef(), + Name: layer1.Name(), + IsVisible: true, + PropertyID: layer1.Property().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "xxx", + "b": float64(1), + }, }, - }, result.Property, "property") - - // plugins - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "hogehoge", + Infobox: nil, + Tags: []tagJSON{ + {ID: tag1.ID().String(), Label: tag1.Label(), Tags: nil}, + {ID: tag4.ID().String(), Label: tag4.Label(), Tags: []tagJSON{ + {ID: tag2.ID().String(), Label: tag2.Label(), Tags: nil}, + }}, }, - }, result.Plugins[pluginID.String()], "plugin1 property") + Children: nil, + } - // widgets - assert.Equal(t, 1, len(result.Widgets), "widgets len") - resWidget1 := result.Widgets[0] - assert.Equal(t, pluginID.String(), resWidget1.PluginID, "widget1 plugin") - assert.Equal(t, string(pluginExtension2ID), resWidget1.ExtensionID, "widget1 extension") - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "hogehoge", + expectedLayer2 := &layerJSON{ + ID: layer2.ID().String(), + PluginID: layer2.Plugin().StringRef(), + ExtensionID: layer2.Extension().StringRef(), + Name: layer2.Name(), + IsVisible: true, + PropertyID: layer2.Property().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "xxx", + "c": "test", + }, }, - }, resWidget1.Property, "widget1 property") - - // layers - assert.Equal(t, 6, len(result.Layers), "layers len") - - // layer1 - resLayer1 := result.Layers[0] - assert.Equal(t, layer1.ID().String(), resLayer1.ID, "layer1 id") - assert.Equal(t, pluginID.StringRef(), resLayer1.PluginID, "layer1 plugin id") - assert.Equal(t, pluginExtension1ID.StringRef(), resLayer1.ExtensionID, "layer1 extension id") - assert.Nil(t, resLayer1.Infobox, "layer1 infobox") - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "xxx", - "b": float64(1), + Infobox: &infoboxJSON{ + Fields: []infoboxFieldJSON{ + { + ID: layer2ibf1.ID().String(), + PluginID: layer2ibf1.Plugin().String(), + ExtensionID: layer2ibf1.Extension().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "xxx", + "c": "test", + }, + }, + }, + }, + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "hogehoge", + }, + }, }, - }, resLayer1.Property, "layer1 prpperty") - - // layer2 - resLayer2 := result.Layers[1] - assert.Equal(t, layer21.ID().String(), resLayer2.ID, "layer2 id") - assert.Equal(t, pluginID.StringRef(), resLayer2.PluginID, "layer2 plugin id") - assert.Equal(t, pluginExtension1ID.StringRef(), resLayer2.ExtensionID, "layer2 extension id") - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "hogehoge", + Tags: []tagJSON{ + {ID: tag1.ID().String(), Label: tag1.Label()}, + {ID: tag4.ID().String(), Label: tag4.Label(), Tags: []tagJSON{ + {ID: tag2.ID().String(), Label: tag2.Label()}, + }}, + }, + Children: []*layerJSON{ + { + ID: layer21.ID().String(), + PluginID: layer21.Plugin().StringRef(), + ExtensionID: layer21.Extension().StringRef(), + Name: layer21.Name(), + IsVisible: true, + PropertyID: layer21.Property().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "yyy", + "b": float64(1), + "c": "test", + }, + }, + Infobox: &infoboxJSON{ + Fields: []infoboxFieldJSON{ + { + ID: layer2ibf1.ID().String(), + PluginID: layer2ibf1.Plugin().String(), + ExtensionID: layer2ibf1.Extension().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "xxx", + "c": "test", + }, + }, + }, + }, + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "hogehoge", + }, + }, + }, + Tags: []tagJSON{ + {ID: tag2.ID().String(), Label: tag2.Label()}, + }, + }, }, - }, resLayer2.Infobox.Property, "layer2 infobox property") - assert.Equal(t, 1, len(resLayer2.Infobox.Fields), "layer2 infobox fields len") - assert.Equal(t, pluginID.String(), resLayer2.Infobox.Fields[0].PluginID, "layer2 infobox field1 plugin") - assert.Equal(t, string(pluginExtension1ID), resLayer2.Infobox.Fields[0].ExtensionID, "layer2 infobox field1 extension") - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "xxx", - "c": "test", + } + + expectedLayer3 := &layerJSON{ + ID: layer3.ID().String(), + PluginID: layer3.Plugin().StringRef(), + ExtensionID: layer3.Extension().StringRef(), + Name: layer3.Name(), + IsVisible: true, + PropertyID: layer3.Property().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "b", + }, }, - }, resLayer2.Infobox.Fields[0].Property, "layer2 infobox field1 property") - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "yyy", - "b": float64(1), - "c": "test", + Infobox: &infoboxJSON{ + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "hogehoge", + }, + }, + Fields: []infoboxFieldJSON{ + { + ID: layer3ibf1.ID().String(), + PluginID: layer3ibf1.Plugin().String(), + ExtensionID: layer3ibf1.Extension().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "hogehoge", + }, + }, + }, + }, }, - }, resLayer2.Property, "layer2 prpperty") + } - // layer3 - resLayer3 := result.Layers[2] - assert.Equal(t, layer3.ID().String(), resLayer3.ID, "layer3 id") - assert.Equal(t, pluginID.StringRef(), resLayer3.PluginID, "layer3 plugin id") - assert.Equal(t, pluginExtension1ID.StringRef(), resLayer3.ExtensionID, "layer3 extension id") - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "hogehoge", + expectedLayer4 := &layerJSON{ + ID: layer4.ID().String(), + PluginID: layer4.Plugin().StringRef(), + ExtensionID: layer4.Extension().StringRef(), + Name: layer4.Name(), + IsVisible: true, + PropertyID: layer4.Property().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": nil, + "c": "xxx", + }, }, - }, resLayer3.Infobox.Property, "layer3 infobox property") - assert.Equal(t, 1, len(resLayer3.Infobox.Fields), "layer3 infobox fields len") - assert.Equal(t, pluginID.String(), resLayer3.Infobox.Fields[0].PluginID, "layer3 infobox field1 plugin") - assert.Equal(t, string(pluginExtension1ID), resLayer3.Infobox.Fields[0].ExtensionID, "layer3 infobox field1 extension") - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "hogehoge", + Infobox: &infoboxJSON{ + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "hogehoge", + }, + }, + Fields: []infoboxFieldJSON{ + { + ID: layer4ibf1.ID().String(), + PluginID: layer4ibf1.Plugin().String(), + ExtensionID: layer4ibf1.Extension().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": nil, + "c": "xxx", + }, + }, + }, + }, }, - }, resLayer3.Infobox.Fields[0].Property, "layer3 infobox field1 property") - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "b", + Children: []*layerJSON{ + { + ID: layer41.ID().String(), + PluginID: layer41.Plugin().StringRef(), + ExtensionID: layer41.Extension().StringRef(), + IsVisible: true, + PropertyID: layer41.Property().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "b", + "b": float64(1), + "c": "xxx", + }, + }, + Infobox: &infoboxJSON{ + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "hogehoge", + "b": float64(1), + }, + }, + Fields: []infoboxFieldJSON{ + { + ID: layer41ibf1.ID().String(), + PluginID: layer41ibf1.Plugin().String(), + ExtensionID: layer41ibf1.Extension().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "b": float64(1), + }, + }, + }, + }, + }, + }, }, - }, resLayer3.Property, "layer3 prpperty") + } - // layer4 - resLayer4 := result.Layers[3] - assert.Equal(t, layer41.ID().String(), resLayer4.ID, "layer4 id") - assert.Equal(t, pluginID.StringRef(), resLayer4.PluginID, "layer4 plugin id") - assert.Equal(t, pluginExtension1ID.StringRef(), resLayer4.ExtensionID, "layer4 extension id") - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "hogehoge", - "b": float64(1), - }, - }, resLayer4.Infobox.Property, "layer4 infobox property") - assert.Equal(t, 1, len(resLayer4.Infobox.Fields), "layer4 infobox fields len") - assert.Equal(t, pluginID.String(), resLayer4.Infobox.Fields[0].PluginID, "layer4 infobox field1 plugin") - assert.Equal(t, string(pluginExtension1ID), resLayer4.Infobox.Fields[0].ExtensionID, "layer4 infobox field1 extension") - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "b": float64(1), + expectedLayer5 := &layerJSON{ + ID: layer5.ID().String(), + PluginID: layer5.Plugin().StringRef(), + ExtensionID: layer5.Extension().StringRef(), + Name: layer5.Name(), + IsVisible: true, + PropertyID: layer5.Property().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": nil, + "b": nil, + }, }, - }, resLayer4.Infobox.Fields[0].Property, "layer4 infobox field1 property") - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "b", - "b": float64(1), - "c": "xxx", + Infobox: nil, + Tags: nil, + Children: []*layerJSON{ + { + ID: layer51.ID().String(), + PluginID: layer51.Plugin().StringRef(), + ExtensionID: layer51.Extension().StringRef(), + IsVisible: true, + PropertyID: layer51.Property().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "a", + "b": "b", + }, + }, + }, }, - }, resLayer4.Property, "layer4 prpperty") + } - // layer5 - resLayer5 := result.Layers[4] - assert.Equal(t, layer51.ID().String(), resLayer5.ID, "layer5 id") - assert.Equal(t, pluginID.StringRef(), resLayer5.PluginID, "layer5 plugin id") - assert.Equal(t, pluginExtension1ID.StringRef(), resLayer5.ExtensionID, "layer5 extension id") - assert.Nil(t, resLayer5.Infobox, "layer5 infobox") - assert.Equal(t, map[string]interface{}{ - "A": map[string]interface{}{ - "a": "a", - "b": "b", + expectedLayer6 := &layerJSON{ + ID: layer6.ID().String(), + PluginID: layer6.Plugin().StringRef(), + ExtensionID: layer6.Extension().StringRef(), + Name: layer6.Name(), + IsVisible: true, + PropertyID: layer6.Property().String(), + Property: map[string]interface{}{ + "B": []map[string]interface{}{ + { + "id": propertyItemID1.String(), + "a": "XYZ", + }, + { + "id": propertyItemID2.String(), + "a": "ZYX", + }, + }, }, - }, resLayer5.Property, "layer5 prpperty") + Infobox: nil, + Tags: nil, + Children: nil, + } - // layer6 - resLayer6 := result.Layers[5] - assert.Equal(t, layer6.ID().String(), resLayer6.ID, "layer6 id") - assert.Equal(t, pluginID.StringRef(), resLayer6.PluginID, "layer6 plugin id") - assert.Equal(t, pluginExtension1ID.StringRef(), resLayer6.ExtensionID, "layer6 extension id") - assert.Nil(t, resLayer6.Infobox, "layer6 infobox") - assert.Equal(t, map[string]interface{}{ - "B": []map[string]interface{}{ - { - "a": "XYZ", - "id": propertyItemID1.String(), + expectedLayers := []*layerJSON{ + expectedLayer1, + expectedLayer2, + expectedLayer3, + expectedLayer4, + expectedLayer5, + expectedLayer6, + } + + expected := &sceneJSON{ + SchemaVersion: version, + ID: sceneID.String(), + PublishedAt: publishedAt, + Layers: expectedLayers, + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "hogehoge", + }, + }, + Plugins: map[string]map[string]interface{}{ + pluginID.String(): { + "A": map[string]interface{}{ + "a": "hogehoge", + }, }, + }, + Widgets: []*widgetJSON{ { - "a": "ZYX", - "id": propertyItemID2.String(), + ID: sceneWidget2.ID().String(), + PluginID: sceneWidget2.Plugin().String(), + ExtensionID: sceneWidget2.Extension().String(), + Property: map[string]interface{}{ + "A": map[string]interface{}{ + "a": "hogehoge", + }, + }, + Extended: true, }, }, - }, resLayer6.Property, "layer6 prpperty") + WidgetAlignSystem: nil, + Tags: []*tagJSON{ + {ID: tag4.ID().String(), Label: tag4.Label(), Tags: []tagJSON{ + {ID: tag1.ID().String(), Label: tag1.Label(), Tags: nil}, + {ID: tag2.ID().String(), Label: tag2.Label(), Tags: nil}, + {ID: tag3.ID().String(), Label: tag3.Label(), Tags: nil}, + }}, + }, + Clusters: []*clusterJSON{}, + } + + // exec + sb := New(lloader, ploader, dloader, tloader, tsloader) + result, err := sb.buildScene(context.Background(), scene, publishedAt) + + assert.NoError(t, err) + assert.Equal(t, expected, result) } diff --git a/pkg/scene/builder/encoder.go b/pkg/scene/builder/encoder.go index eb5f2cac..28d772e1 100644 --- a/pkg/scene/builder/encoder.go +++ b/pkg/scene/builder/encoder.go @@ -9,59 +9,79 @@ import ( var _ encoding.Encoder = &encoder{} type encoder struct { - res []*layerJSON + res *layerJSON } func (e *encoder) Result() []*layerJSON { - if e == nil { + if e == nil || e.res == nil { return nil } - return e.res + return e.res.Children } func (e *encoder) Encode(l merging.SealedLayer) (err error) { if e == nil { return } - e.res = e.layers(l) + e.res = e.layer(l) return } -func (e *encoder) layers(l merging.SealedLayer) []*layerJSON { +func (e *encoder) layer(layer merging.SealedLayer) *layerJSON { + if layer == nil { + return nil + } + l := layer.Common() if l == nil { return nil } - if i, ok := l.(*merging.SealedLayerItem); ok { - layer := e.layer(i) - if layer == nil { - return nil - } - return []*layerJSON{layer} - } else if g, ok := l.(*merging.SealedLayerGroup); ok { - // This encoder does not print group layer representation. - layers := make([]*layerJSON, 0, len(g.Children)) + + var children []*layerJSON + if g := layer.Group(); g != nil { for _, c := range g.Children { - l := e.layers(c) - if l != nil { - layers = append(layers, l...) + if d := e.layer(c); d != nil { + children = append(children, d) } } - return layers } - return nil -} -func (e *encoder) layer(l *merging.SealedLayerItem) *layerJSON { - if l == nil { - return nil + var propertyID string + if l.Property != nil { + propertyID = l.Property.Original.String() } + + var tags []tagJSON + if len(l.Tags) > 0 { + for _, t := range l.Tags { + var tags2 []tagJSON + if len(t.Tags) > 0 { + tags2 = make([]tagJSON, 0, len(t.Tags)) + for _, t := range t.Tags { + tags2 = append(tags2, tagJSON{ + ID: t.ID.String(), + Label: t.Label, + }) + } + } + tags = append(tags, tagJSON{ + ID: t.ID.String(), + Label: t.Label, + Tags: tags2, + }) + } + } + return &layerJSON{ ID: l.Original.String(), PluginID: l.PluginID.StringRef(), ExtensionID: l.ExtensionID.StringRef(), Name: l.Name, Property: e.property(l.Property), + PropertyID: propertyID, Infobox: e.infobox(l.Infobox), + IsVisible: l.IsVisible, + Tags: tags, + Children: children, } } @@ -93,8 +113,18 @@ type layerJSON struct { PluginID *string `json:"pluginId,omitempty"` ExtensionID *string `json:"extensionId,omitempty"` Name string `json:"name,omitempty"` + PropertyID string `json:"propertyId,omitempty"` Property propertyJSON `json:"property,omitempty"` Infobox *infoboxJSON `json:"infobox,omitempty"` + Tags []tagJSON `json:"tags,omitempty"` + IsVisible bool `json:"isVisible"` + Children []*layerJSON `json:"children,omitempty"` +} + +type tagJSON struct { + ID string `json:"id"` + Label string `json:"label"` + Tags []tagJSON `json:"tags,omitempty"` } type infoboxJSON struct { diff --git a/pkg/scene/builder/encoder_test.go b/pkg/scene/builder/encoder_test.go index ef81c0f6..68228976 100644 --- a/pkg/scene/builder/encoder_test.go +++ b/pkg/scene/builder/encoder_test.go @@ -13,25 +13,27 @@ import ( func TestEncoder_Result(t *testing.T) { tests := []struct { Name string - E *encoder + Target *encoder Expected []*layerJSON }{ { Name: "nil encoder", - E: nil, + Target: nil, Expected: nil, }, { Name: "success", - E: &encoder{ - res: []*layerJSON{ - { - ID: "xxx", - PluginID: nil, - ExtensionID: nil, - Name: "aaa", - Property: nil, - Infobox: nil, + Target: &encoder{ + res: &layerJSON{ + Children: []*layerJSON{ + { + ID: "xxx", + PluginID: nil, + ExtensionID: nil, + Name: "aaa", + Property: nil, + Infobox: nil, + }, }, }, }, @@ -52,7 +54,7 @@ func TestEncoder_Result(t *testing.T) { tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() - res := tc.E.Result() + res := tc.Target.Result() assert.Equal(t, tc.Expected, res) }) } @@ -61,20 +63,20 @@ func TestEncoder_Result(t *testing.T) { func TestEncoder_Encode(t *testing.T) { tests := []struct { Name string - E *encoder - SL merging.SealedLayer + Target *encoder + Input merging.SealedLayer Expected error }{ { Name: "nil encoder", - E: nil, - SL: nil, + Target: nil, + Input: nil, Expected: nil, }, { Name: "success encoding", - E: &encoder{}, - SL: nil, + Target: &encoder{}, + Input: nil, Expected: nil, }, } @@ -83,7 +85,7 @@ func TestEncoder_Encode(t *testing.T) { tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() - res := tc.E.Encode(tc.SL) + res := tc.Target.Encode(tc.Input) assert.Equal(t, tc.Expected, res) }) } @@ -139,31 +141,37 @@ func TestEncoder_Layers(t *testing.T) { }, Property: &sp, Infobox: nil, - }} + }, + } tests := []struct { Name string - E *encoder - SL *merging.SealedLayerItem + Target *encoder + Input *merging.SealedLayerItem Expected *layerJSON }{ { Name: "nil layers", - E: &encoder{}, - SL: nil, + Target: &encoder{}, + Input: nil, Expected: nil, }, { - Name: "success", - E: &encoder{}, - SL: sealed, + Name: "success", + Target: &encoder{}, + Input: sealed, Expected: &layerJSON{ ID: lid.String(), PluginID: layer.OfficialPluginID.StringRef(), ExtensionID: ex.StringRef(), Name: "test", - Property: map[string]interface{}{"default": map[string]interface{}{"location": property.LatLng{Lat: 4.4, Lng: 53.4}}}, - Infobox: nil, + PropertyID: pid.String(), + Property: map[string]interface{}{ + "default": map[string]interface{}{ + "location": property.LatLng{Lat: 4.4, Lng: 53.4}, + }, + }, + Infobox: nil, }, }, } @@ -172,17 +180,8 @@ func TestEncoder_Layers(t *testing.T) { tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() - res := tc.E.layer(tc.SL) - if res == nil { - assert.Equal(t, tc.Expected, res) - } else { - assert.Equal(t, tc.Expected.Property, res.Property) - assert.Equal(t, tc.Expected.Infobox, res.Infobox) - assert.Equal(t, *tc.Expected.ExtensionID, *res.ExtensionID) - assert.Equal(t, tc.Expected.ID, res.ID) - assert.Equal(t, tc.Expected.Name, res.Name) - assert.Equal(t, *tc.Expected.PluginID, *res.PluginID) - } + res := tc.Target.layer(tc.Input) + assert.Equal(t, tc.Expected, res) }) } } diff --git a/pkg/scene/builder/scene.go b/pkg/scene/builder/scene.go index 7d17193e..fcdbd936 100644 --- a/pkg/scene/builder/scene.go +++ b/pkg/scene/builder/scene.go @@ -6,6 +6,7 @@ import ( "github.com/reearth/reearth-backend/pkg/property" "github.com/reearth/reearth-backend/pkg/scene" + "github.com/reearth/reearth-backend/pkg/tag" ) type sceneJSON struct { @@ -17,10 +18,16 @@ type sceneJSON struct { Layers []*layerJSON `json:"layers"` Widgets []*widgetJSON `json:"widgets"` WidgetAlignSystem *widgetAlignSystemJSON `json:"widgetAlignSystem"` + Tags []*tagJSON `json:"tags"` Clusters []*clusterJSON `json:"clusters"` } -func (b *Builder) scene(ctx context.Context, s *scene.Scene, publishedAt time.Time, l []*layerJSON, p []*property.Property) *sceneJSON { +func (b *Builder) scene(ctx context.Context, s *scene.Scene, publishedAt time.Time, l []*layerJSON, p []*property.Property) (*sceneJSON, error) { + tags, err := b.tags(ctx, s) + if err != nil { + return nil, err + } + return &sceneJSON{ SchemaVersion: version, ID: s.ID().String(), @@ -30,8 +37,9 @@ func (b *Builder) scene(ctx context.Context, s *scene.Scene, publishedAt time.Ti Widgets: b.widgets(ctx, s, p), Clusters: b.clusters(ctx, s, p), Layers: l, + Tags: tags, WidgetAlignSystem: buildWidgetAlignSystem(s.WidgetAlignSystem()), - } + }, nil } func (b *Builder) plugins(ctx context.Context, s *scene.Scene, p []*property.Property) map[string]propertyJSON { @@ -80,6 +88,45 @@ func (b *Builder) clusters(ctx context.Context, s *scene.Scene, p []*property.Pr return res } +func (b *Builder) tags(ctx context.Context, s *scene.Scene) ([]*tagJSON, error) { + tags, err := b.tloader(ctx, s.ID()) + if err != nil { + return nil, err + } + tagMap := tag.MapFromRefList(tags) + rootTags := tag.DerefList(tags).Roots() + stags := make([]*tagJSON, 0, len(rootTags)) + for _, t := range rootTags { + if t == nil { + continue + } + t2 := toTag(t, tagMap) + stags = append(stags, &t2) + } + return stags, nil +} + +func toTag(t tag.Tag, m tag.Map) tagJSON { + var tags []tagJSON + if children := tag.GroupFrom(t).Tags().Tags(); children != nil { + tags = make([]tagJSON, 0, len(children)) + for _, tid := range children { + t, ok := m[tid] + if !ok { + continue + } + t2 := toTag(t, m) + tags = append(tags, t2) + } + } + + return tagJSON{ + ID: t.ID().String(), + Label: t.Label(), + Tags: tags, + } +} + func (b *Builder) property(ctx context.Context, p *property.Property) propertyJSON { return property.SealProperty(ctx, p).Interface() } diff --git a/pkg/tag/group.go b/pkg/tag/group.go index 7dcc6c8a..0c9c9a88 100644 --- a/pkg/tag/group.go +++ b/pkg/tag/group.go @@ -2,9 +2,12 @@ package tag type Group struct { tag - tags *List + tags *IDList } -func (g *Group) Tags() *List { +func (g *Group) Tags() *IDList { + if g == nil { + return nil + } return g.tags } diff --git a/pkg/tag/group_builder.go b/pkg/tag/group_builder.go index 4774ca48..77283cb6 100644 --- a/pkg/tag/group_builder.go +++ b/pkg/tag/group_builder.go @@ -26,10 +26,17 @@ func (b *GroupBuilder) Build() (*Group, error) { if b.g.label == "" { return nil, ErrEmptyLabel } - return b.g, nil } +func (b *GroupBuilder) MustBuild() *Group { + res, err := b.Build() + if err != nil { + panic(err) + } + return res +} + func (b *GroupBuilder) ID(tid ID) *GroupBuilder { b.g.id = tid return b @@ -50,7 +57,7 @@ func (b *GroupBuilder) Scene(sid SceneID) *GroupBuilder { return b } -func (b *GroupBuilder) Tags(tl *List) *GroupBuilder { +func (b *GroupBuilder) Tags(tl *IDList) *GroupBuilder { if tl != nil { b.g.tags = tl } diff --git a/pkg/tag/group_test.go b/pkg/tag/group_test.go index 1b95edc8..2be71f42 100644 --- a/pkg/tag/group_test.go +++ b/pkg/tag/group_test.go @@ -25,7 +25,7 @@ func TestGroupBuilder_Build(t *testing.T) { Name, Label string Id ID Scene SceneID - Tags *List + Tags *IDList Expected struct { Group Group Error error @@ -69,7 +69,7 @@ func TestGroupBuilder_Build(t *testing.T) { Id: tid, Label: "xxx", Scene: sid, - Tags: &List{ + Tags: &IDList{ tags: tags, }, Expected: struct { @@ -82,7 +82,7 @@ func TestGroupBuilder_Build(t *testing.T) { label: "xxx", sceneId: sid, }, - tags: &List{ + tags: &IDList{ tags: tags, }, }, diff --git a/pkg/tag/item.go b/pkg/tag/item.go index 34ad6b09..696dcaf0 100644 --- a/pkg/tag/item.go +++ b/pkg/tag/item.go @@ -9,18 +9,30 @@ type Item struct { } func (i *Item) Parent() *ID { + if i == nil { + return nil + } return i.parent.CopyRef() } func (i *Item) LinkedDatasetFieldID() *DatasetFieldID { + if i == nil { + return nil + } return i.linkedDatasetFieldID.CopyRef() } func (i *Item) LinkedDatasetID() *DatasetID { + if i == nil { + return nil + } return i.linkedDatasetID.CopyRef() } func (i *Item) LinkedDatasetSchemaID() *DatasetSchemaID { + if i == nil { + return nil + } return i.linkedDatasetSchemaID.CopyRef() } diff --git a/pkg/tag/item_builder.go b/pkg/tag/item_builder.go index ed2a048a..29eb8790 100644 --- a/pkg/tag/item_builder.go +++ b/pkg/tag/item_builder.go @@ -29,6 +29,14 @@ func (b *ItemBuilder) Build() (*Item, error) { return b.i, nil } +func (b *ItemBuilder) MustBuild() *Item { + res, err := b.Build() + if err != nil { + panic(err) + } + return res +} + func (b *ItemBuilder) ID(tid ID) *ItemBuilder { b.i.id = tid return b diff --git a/pkg/tag/list.go b/pkg/tag/list.go index 320b4de2..42ee1d35 100644 --- a/pkg/tag/list.go +++ b/pkg/tag/list.go @@ -1,25 +1,25 @@ package tag -type List struct { +type IDList struct { tags []ID } -func NewList() *List { - return &List{tags: []ID{}} +func NewIDList() *IDList { + return &IDList{tags: []ID{}} } -func NewListFromTags(tags []ID) *List { - return &List{tags: tags} +func IDListFrom(tags []ID) *IDList { + return &IDList{tags: tags} } -func (tl *List) Tags() []ID { - if tl == nil || tl.tags == nil { +func (tl *IDList) Tags() []ID { + if tl == nil || len(tl.tags) == 0 { return nil } return append([]ID{}, tl.tags...) } -func (tl *List) Has(tid ID) bool { +func (tl *IDList) Has(tid ID) bool { if tl == nil || tl.tags == nil { return false } @@ -31,14 +31,14 @@ func (tl *List) Has(tid ID) bool { return false } -func (tl *List) Add(tags ...ID) { +func (tl *IDList) Add(tags ...ID) { if tl == nil || tl.tags == nil { return } tl.tags = append(tl.tags, tags...) } -func (tl *List) Remove(tags ...ID) { +func (tl *IDList) Remove(tags ...ID) { if tl == nil || tl.tags == nil { return } @@ -52,3 +52,99 @@ func (tl *List) Remove(tags ...ID) { } } } + +type List []Tag + +func DerefList(tags []*Tag) List { + res := make(List, 0, len(tags)) + for _, t := range tags { + if t == nil { + continue + } + res = append(res, *t) + } + return res +} + +func (l List) Items() (res []*Item) { + if len(l) == 0 { + return + } + + res = make([]*Item, 0, len(l)) + for _, t := range l { + if g := ItemFrom(t); g != nil { + res = append(res, g) + } + } + + return res +} + +func (l List) Groups() (res []*Group) { + if len(l) == 0 { + return + } + + res = make([]*Group, 0, len(l)) + for _, t := range l { + if g := GroupFrom(t); g != nil { + res = append(res, g) + } + } + + return res +} + +func (l List) FilterByScene(s SceneID) (res List) { + if len(l) == 0 { + return + } + + res = make(List, 0, len(l)) + for _, t := range l { + if t.Scene() == s { + res = append(res, t) + } + } + + return res +} + +func (l List) Roots() (res List) { + if len(l) == 0 { + return + } + + groups := l.Groups() + for _, t := range l { + found := false + for _, u := range groups { + if t.ID() == u.ID() { + continue + } + if u.Tags().Has(t.ID()) { + found = true + } + } + if !found { + res = append(res, t) + } + } + + return res +} + +func (l List) Refs() (res []*Tag) { + if len(l) == 0 { + return + } + + res = make([]*Tag, 0, len(l)) + for _, t := range l { + t := t + res = append(res, &t) + } + + return res +} diff --git a/pkg/tag/list_test.go b/pkg/tag/list_test.go index f7328958..59208d91 100644 --- a/pkg/tag/list_test.go +++ b/pkg/tag/list_test.go @@ -6,34 +6,34 @@ import ( "github.com/stretchr/testify/assert" ) -func TestList_Add(t *testing.T) { +func TesIDtList_Add(t *testing.T) { tid := NewID() - var tl *List + var tl *IDList tl.Add(tid) assert.Nil(t, tl.Tags()) - tl = NewList() + tl = NewIDList() tl.Add(tid) expected := []ID{tid} assert.Equal(t, expected, tl.Tags()) } -func TestList_Remove(t *testing.T) { +func TestIDList_Remove(t *testing.T) { tid := NewID() tid2 := NewID() tags := []ID{ tid, tid2, } - var tl *List + var tl *IDList tl.Remove(tid2) assert.Nil(t, tl.Tags()) - tl = NewListFromTags(tags) + tl = IDListFrom(tags) tl.Remove(tid2) expected := []ID{tid} assert.Equal(t, expected, tl.Tags()) } -func TestList_Has(t *testing.T) { +func TestIDList_Has(t *testing.T) { tid1 := NewID() tid2 := NewID() tags := []ID{ @@ -68,8 +68,83 @@ func TestList_Has(t *testing.T) { tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() - res := NewListFromTags(tc.Tags).Has(tc.TID) + res := IDListFrom(tc.Tags).Has(tc.TID) assert.Equal(t, tc.Expected, res) }) } } + +func TestList_Items(t *testing.T) { + sceneID := NewSceneID() + sceneID2 := NewSceneID() + tag1 := NewItem().NewID().Label("hoge").Scene(sceneID).MustBuild() + tag2 := NewItem().NewID().Label("foo").Scene(sceneID).MustBuild() + tag3 := NewItem().NewID().Label("foo").Scene(sceneID2).MustBuild() + tag4 := NewGroup().NewID().Label("bar").Scene(sceneID).Tags(IDListFrom(([]ID{ + tag1.ID(), tag2.ID(), + }))).MustBuild() + tags := List{tag1, tag2, tag3, tag4} + + assert.Equal(t, []*Item{tag1, tag2, tag3}, tags.Items()) + assert.Nil(t, List(nil).Items()) +} + +func TestList_Groups(t *testing.T) { + sceneID := NewSceneID() + sceneID2 := NewSceneID() + tag1 := NewItem().NewID().Label("hoge").Scene(sceneID).MustBuild() + tag2 := NewItem().NewID().Label("foo").Scene(sceneID).MustBuild() + tag3 := NewItem().NewID().Label("foo").Scene(sceneID2).MustBuild() + tag4 := NewGroup().NewID().Label("bar").Scene(sceneID).Tags(IDListFrom(([]ID{ + tag1.ID(), tag2.ID(), + }))).MustBuild() + tags := List{tag1, tag2, tag3, tag4} + + assert.Equal(t, []*Group{tag4}, tags.Groups()) + assert.Nil(t, List(nil).Groups()) +} + +func TestList_FilterByScene(t *testing.T) { + sceneID := NewSceneID() + sceneID2 := NewSceneID() + tag1 := NewItem().NewID().Label("hoge").Scene(sceneID).MustBuild() + tag2 := NewItem().NewID().Label("foo").Scene(sceneID).MustBuild() + tag3 := NewItem().NewID().Label("foo").Scene(sceneID2).MustBuild() + tag4 := NewGroup().NewID().Label("bar").Scene(sceneID).Tags(IDListFrom(([]ID{ + tag1.ID(), tag2.ID(), + }))).MustBuild() + tags := List{tag1, tag2, tag3, tag4} + + assert.Equal(t, List{tag1, tag2, tag4}, tags.FilterByScene(sceneID)) + assert.Nil(t, List(nil).FilterByScene(sceneID)) +} + +func TestList_Roots(t *testing.T) { + sceneID := NewSceneID() + sceneID2 := NewSceneID() + tag1 := NewItem().NewID().Label("hoge").Scene(sceneID).MustBuild() + tag2 := NewItem().NewID().Label("foo").Scene(sceneID).MustBuild() + tag3 := NewItem().NewID().Label("foo").Scene(sceneID2).MustBuild() + tag4 := NewGroup().NewID().Label("bar").Scene(sceneID).Tags(IDListFrom(([]ID{ + tag1.ID(), tag2.ID(), + }))).MustBuild() + tags := List{tag1, tag2, tag3, tag4} + + assert.Equal(t, List{tag3, tag4}, tags.Roots()) + assert.Nil(t, List(nil).Roots()) +} + +func TestList_Refs(t *testing.T) { + sceneID := NewSceneID() + sceneID2 := NewSceneID() + var tag1 Tag = NewItem().NewID().Label("hoge").Scene(sceneID).MustBuild() + var tag2 Tag = NewItem().NewID().Label("foo").Scene(sceneID).MustBuild() + var tag3 Tag = NewItem().NewID().Label("foo").Scene(sceneID2).MustBuild() + var tag4 Tag = NewGroup().NewID().Label("bar").Scene(sceneID).Tags(IDListFrom(([]ID{ + tag1.ID(), tag2.ID(), + }))).MustBuild() + tags := List{tag1, tag2, tag3, tag4} + + assert.Equal(t, []*Tag{&tag1, &tag2, &tag3, &tag4}, tags.Refs()) + assert.Nil(t, List(nil).Refs()) +} diff --git a/pkg/tag/loader.go b/pkg/tag/loader.go new file mode 100644 index 00000000..b90b3511 --- /dev/null +++ b/pkg/tag/loader.go @@ -0,0 +1,46 @@ +package tag + +import "context" + +type Loader func(context.Context, ...ID) ([]*Tag, error) +type SceneLoader func(context.Context, SceneID) ([]*Tag, error) + +func LoaderFrom(data List) Loader { + return func(ctx context.Context, ids ...ID) ([]*Tag, error) { + res := make([]*Tag, 0, len(ids)) + for _, i := range ids { + found := false + for _, d := range data { + if i == d.ID() { + res = append(res, &d) + found = true + break + } + } + if !found { + res = append(res, nil) + } + } + return res, nil + } +} + +func LoaderFromMap(data map[ID]Tag) Loader { + return func(ctx context.Context, ids ...ID) ([]*Tag, error) { + res := make([]*Tag, 0, len(ids)) + for _, i := range ids { + if d, ok := data[i]; ok { + res = append(res, &d) + } else { + res = append(res, nil) + } + } + return res, nil + } +} + +func SceneLoaderFrom(data List) SceneLoader { + return func(ctx context.Context, id SceneID) ([]*Tag, error) { + return data.FilterByScene(id).Refs(), nil + } +} diff --git a/pkg/tag/map.go b/pkg/tag/map.go new file mode 100644 index 00000000..71557e1e --- /dev/null +++ b/pkg/tag/map.go @@ -0,0 +1,48 @@ +package tag + +import "sort" + +type Map map[ID]Tag + +func (m Map) All() List { + if m == nil || len(m) == 0 { + return nil + } + res := make(List, 0, len(m)) + for _, t := range m { + res = append(res, t) + } + sort.SliceStable(res, func(i, j int) bool { + return res[i].ID().ID().Compare(res[j].ID().ID()) < 0 + }) + return res +} + +func MapFromList(tags []Tag) Map { + res := make(Map) + for _, t := range tags { + if t == nil { + continue + } + + res[t.ID()] = t + } + return res +} + +func MapFromRefList(tags []*Tag) Map { + res := make(Map) + for _, t := range tags { + if t == nil { + continue + } + + t2 := *t + if t2 == nil { + continue + } + + res[t2.ID()] = t2 + } + return res +}