From f28bdc08fd88928b49b410af72e7ebf9dcd885a0 Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Fri, 29 Nov 2024 17:15:43 +0100 Subject: [PATCH 1/6] feat: datasource for `gno.land/r/leon/hof` integration with Gno.me This is quick initial PoC of a Gno.me integration idea. --- examples/gno.land/r/leon/hof/datasource.gno | 64 +++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 examples/gno.land/r/leon/hof/datasource.gno diff --git a/examples/gno.land/r/leon/hof/datasource.gno b/examples/gno.land/r/leon/hof/datasource.gno new file mode 100644 index 00000000000..da1feb600ed --- /dev/null +++ b/examples/gno.land/r/leon/hof/datasource.gno @@ -0,0 +1,64 @@ +package hof + +import ( + "errors" + + "gno.land/p/demo/seqid" + "gno.land/p/demo/ufmt" + "gno.land/p/gnome/datasource" +) + +type Datasource struct{} + +func (ds Datasource) GetRecords(offset, _ int, _ string) datasource.RecordIter { + return &iterator{index: offset} +} + +func (ds Datasource) GetContent(rawID string, fn func(content string)) error { + id, err := seqid.FromString(rawID) + if err != nil { + return err + } + + v, ok := exhibition.itemsSorted.Get(id.String()) + if !ok { + return errors.New("realm submission not found") + } + + content := ufmt.Sprintf("# Submission #%d\n\n", int(id)) + content += v.(*Item).Render(false) + + fn(content) + return nil +} + +type record struct { + item *Item +} + +func (r record) ID() string { return r.item.id.String() } +func (r record) Title() string { return r.item.pkgpath } +func (r record) Tags() []string { return nil } + +func (r record) Details() string { + return ufmt.Sprintf("Votes: ⏶ %d - ⏷ %d", r.item.upvote.Size(), r.item.downvote.Size()) +} + +type iterator struct { + index int + record *record +} + +func (it iterator) Record() datasource.Record { return it.record } +func (it iterator) Err() error { return nil } + +func (it *iterator) Next() bool { + if it.index >= exhibition.itemsSorted.Size() { + return false + } + + _, v := exhibition.itemsSorted.GetByIndex(it.index) + it.record = &record{v.(*Item)} + it.index++ + return true +} From ae1dda2dbc014be5678c5f3549297ce9d25453eb Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Fri, 29 Nov 2024 17:32:47 +0100 Subject: [PATCH 2/6] chore: rename `RecordIter` to `RecordIterator` --- examples/gno.land/r/leon/hof/datasource.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/leon/hof/datasource.gno b/examples/gno.land/r/leon/hof/datasource.gno index da1feb600ed..7ab89db5b60 100644 --- a/examples/gno.land/r/leon/hof/datasource.gno +++ b/examples/gno.land/r/leon/hof/datasource.gno @@ -10,7 +10,7 @@ import ( type Datasource struct{} -func (ds Datasource) GetRecords(offset, _ int, _ string) datasource.RecordIter { +func (ds Datasource) GetRecords(offset, _ int, _ string) datasource.RecordIterator { return &iterator{index: offset} } From b0d6c948f49fca67be67fc493cf18a03f387fc44 Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Wed, 11 Dec 2024 15:38:38 +0100 Subject: [PATCH 3/6] feat: change datasource to use a different package --- examples/gno.land/r/leon/hof/datasource.gno | 64 ++++--- .../gno.land/r/leon/hof/datasource_test.gno | 156 ++++++++++++++++++ 2 files changed, 195 insertions(+), 25 deletions(-) create mode 100644 examples/gno.land/r/leon/hof/datasource_test.gno diff --git a/examples/gno.land/r/leon/hof/datasource.gno b/examples/gno.land/r/leon/hof/datasource.gno index 7ab89db5b60..4647b08b979 100644 --- a/examples/gno.land/r/leon/hof/datasource.gno +++ b/examples/gno.land/r/leon/hof/datasource.gno @@ -3,33 +3,36 @@ package hof import ( "errors" + "gno.land/p/demo/avl" "gno.land/p/demo/seqid" "gno.land/p/demo/ufmt" - "gno.land/p/gnome/datasource" + "gno.land/p/jeronimoalbi/datasource" ) -type Datasource struct{} +func NewDatasource() Datasource { + return Datasource{exhibition} +} -func (ds Datasource) GetRecords(offset, _ int, _ string) datasource.RecordIterator { - return &iterator{index: offset} +type Datasource struct { + exhibition *Exhibition } -func (ds Datasource) GetContent(rawID string, fn func(content string)) error { - id, err := seqid.FromString(rawID) - if err != nil { - return err - } +func (ds Datasource) Size() int { return ds.exhibition.itemsSorted.Size() } - v, ok := exhibition.itemsSorted.Get(id.String()) - if !ok { - return errors.New("realm submission not found") +func (ds Datasource) Records(q datasource.Query) datasource.Iterator { + return &iterator{ + exhibition: ds.exhibition, + index: q.Offset, + maxIndex: q.Offset + q.Count, } +} - content := ufmt.Sprintf("# Submission #%d\n\n", int(id)) - content += v.(*Item).Render(false) - - fn(content) - return nil +func (ds Datasource) Record(id string) (datasource.Record, error) { + v, found := ds.exhibition.itemsSorted.Get(id) + if !found { + return nil, errors.New("realm submission not found") + } + return record{v.(*Item)}, nil } type record struct { @@ -37,27 +40,38 @@ type record struct { } func (r record) ID() string { return r.item.id.String() } -func (r record) Title() string { return r.item.pkgpath } -func (r record) Tags() []string { return nil } +func (r record) String() string { return r.item.pkgpath } + +func (r record) Fields() (datasource.Fields, error) { + fields := avl.NewTree() + fields.Set( + "details", + ufmt.Sprintf("Votes: ⏶ %d - ⏷ %d", r.item.upvote.Size(), r.item.downvote.Size()), + ) + return fields, nil +} -func (r record) Details() string { - return ufmt.Sprintf("Votes: ⏶ %d - ⏷ %d", r.item.upvote.Size(), r.item.downvote.Size()) +func (r record) Content() (string, error) { + content := ufmt.Sprintf("# Submission #%s\n\n", r.item.id.String()) + content += r.item.Render(false) + return content, nil } type iterator struct { - index int - record *record + exhibition *Exhibition + index, maxIndex int + record *record } func (it iterator) Record() datasource.Record { return it.record } func (it iterator) Err() error { return nil } func (it *iterator) Next() bool { - if it.index >= exhibition.itemsSorted.Size() { + if it.index >= it.maxIndex || it.index >= it.exhibition.itemsSorted.Size() { return false } - _, v := exhibition.itemsSorted.GetByIndex(it.index) + _, v := it.exhibition.itemsSorted.GetByIndex(it.index) it.record = &record{v.(*Item)} it.index++ return true diff --git a/examples/gno.land/r/leon/hof/datasource_test.gno b/examples/gno.land/r/leon/hof/datasource_test.gno new file mode 100644 index 00000000000..d3aec67f024 --- /dev/null +++ b/examples/gno.land/r/leon/hof/datasource_test.gno @@ -0,0 +1,156 @@ +package hof + +import ( + "testing" + + "gno.land/p/demo/avl" + "gno.land/p/demo/uassert" + "gno.land/p/demo/urequire" + "gno.land/p/jeronimoalbi/datasource" +) + +var ( + _ datasource.Datasource = (*Datasource)(nil) + _ datasource.Record = (*record)(nil) + _ datasource.Iterator = (*iterator)(nil) +) + +func TestDatasourceRecords(t *testing.T) { + cases := []struct { + name string + items []*Item + recordIDs []string + options []datasource.QueryOption + }{ + { + name: "all items", + items: []*Item{{id: 1}, {id: 2}, {id: 3}}, + recordIDs: []string{"0000001", "0000002", "0000003"}, + }, + { + name: "with offset", + items: []*Item{{id: 1}, {id: 2}, {id: 3}}, + recordIDs: []string{"0000002", "0000003"}, + options: []datasource.QueryOption{datasource.WithOffset(1)}, + }, + { + name: "with count", + items: []*Item{{id: 1}, {id: 2}, {id: 3}}, + recordIDs: []string{"0000001", "0000002"}, + options: []datasource.QueryOption{datasource.WithCount(2)}, + }, + { + name: "with offset and count", + items: []*Item{{id: 1}, {id: 2}, {id: 3}}, + recordIDs: []string{"0000002"}, + options: []datasource.QueryOption{ + datasource.WithOffset(1), + datasource.WithCount(1), + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Initialize a local instance of exhibition + exhibition := &Exhibition{itemsSorted: avl.NewTree()} + for _, item := range tc.items { + exhibition.itemsSorted.Set(item.id.String(), item) + } + + // Get a records iterator + ds := Datasource{exhibition} + query := datasource.NewQuery(tc.options...) + iter := ds.Records(query) + + // Start asserting + urequire.Equal(t, len(tc.items), ds.Size(), "datasource size") + + var records []datasource.Record + for iter.Next() { + records = append(records, iter.Record()) + } + urequire.Equal(t, len(tc.recordIDs), len(records), "record count") + + for i, r := range records { + uassert.Equal(t, tc.recordIDs[i], r.ID()) + } + }) + } +} + +func TestDatasourceRecord(t *testing.T) { + cases := []struct { + name string + items []*Item + id string + err string + }{ + { + name: "found", + items: []*Item{{id: 1}, {id: 2}, {id: 3}}, + id: "0000001", + }, + { + name: "no found", + items: []*Item{{id: 1}, {id: 2}, {id: 3}}, + id: "42", + err: "realm submission not found", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + // Initialize a local instance of exhibition + exhibition := &Exhibition{itemsSorted: avl.NewTree()} + for _, item := range tc.items { + exhibition.itemsSorted.Set(item.id.String(), item) + } + + // Get a single record + ds := Datasource{exhibition} + r, err := ds.Record(tc.id) + + // Start asserting + if tc.err != "" { + uassert.ErrorContains(t, err, tc.err) + return + } + + urequire.NoError(t, err, "no error") + urequire.NotEqual(t, nil, r, "record not nil") + uassert.Equal(t, tc.id, r.ID()) + }) + } +} + +func TestItemRecord(t *testing.T) { + pkgpath := "gno.land/r/demo/test" + item := Item{ + id: 1, + pkgpath: pkgpath, + blockNum: 42, + upvote: avl.NewTree(), + downvote: avl.NewTree(), + } + item.downvote.Set("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", struct{}{}) + item.upvote.Set("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", struct{}{}) + item.upvote.Set("g1w4ek2u3jta047h6lta047h6lta047h6l9huexc", struct{}{}) + + r := record{&item} + + uassert.Equal(t, "0000001", r.ID()) + uassert.Equal(t, pkgpath, r.String()) + + fields, _ := r.Fields() + details, found := fields.Get("details") + urequire.True(t, found, "details field") + uassert.Equal(t, "Votes: ⏶ 2 - ⏷ 1", details) + + content, _ := r.Content() + wantContent := "# Submission #0000001\n\n\n```\ngno.land/r/demo/test\n```\n\nby demo\n\n" + + "[View realm](/r/demo/test)\n\nSubmitted at Block #42\n\n" + + "#### [2👍](/r/leon/hof$help&func=Upvote&pkgpath=gno.land/r/demo/test) - " + + "[1👎](/r/leon/hof$help&func=Downvote&pkgpath=gno.land/r/demo/test)\n\n" + uassert.Equal(t, wantContent, content) +} From 4ee02c1968cc81ca8c74f5a15acfedce492ac39c Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Wed, 11 Dec 2024 15:51:22 +0100 Subject: [PATCH 4/6] chore: remove unused import --- examples/gno.land/r/leon/hof/datasource.gno | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/gno.land/r/leon/hof/datasource.gno b/examples/gno.land/r/leon/hof/datasource.gno index 4647b08b979..b4e3572b175 100644 --- a/examples/gno.land/r/leon/hof/datasource.gno +++ b/examples/gno.land/r/leon/hof/datasource.gno @@ -4,7 +4,6 @@ import ( "errors" "gno.land/p/demo/avl" - "gno.land/p/demo/seqid" "gno.land/p/demo/ufmt" "gno.land/p/jeronimoalbi/datasource" ) From 7508a05e6648eb5adc9013bc1ec993341fa6f20e Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Wed, 11 Dec 2024 17:35:55 +0100 Subject: [PATCH 5/6] chore: change submission ID format for datasource record --- examples/gno.land/r/leon/hof/datasource.gno | 2 +- examples/gno.land/r/leon/hof/datasource_test.gno | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/r/leon/hof/datasource.gno b/examples/gno.land/r/leon/hof/datasource.gno index b4e3572b175..180c4880177 100644 --- a/examples/gno.land/r/leon/hof/datasource.gno +++ b/examples/gno.land/r/leon/hof/datasource.gno @@ -51,7 +51,7 @@ func (r record) Fields() (datasource.Fields, error) { } func (r record) Content() (string, error) { - content := ufmt.Sprintf("# Submission #%s\n\n", r.item.id.String()) + content := ufmt.Sprintf("# Submission #%d\n\n", int(r.item.id)) content += r.item.Render(false) return content, nil } diff --git a/examples/gno.land/r/leon/hof/datasource_test.gno b/examples/gno.land/r/leon/hof/datasource_test.gno index d3aec67f024..ad605db32f0 100644 --- a/examples/gno.land/r/leon/hof/datasource_test.gno +++ b/examples/gno.land/r/leon/hof/datasource_test.gno @@ -148,7 +148,7 @@ func TestItemRecord(t *testing.T) { uassert.Equal(t, "Votes: ⏶ 2 - ⏷ 1", details) content, _ := r.Content() - wantContent := "# Submission #0000001\n\n\n```\ngno.land/r/demo/test\n```\n\nby demo\n\n" + + wantContent := "# Submission #1\n\n\n```\ngno.land/r/demo/test\n```\n\nby demo\n\n" + "[View realm](/r/demo/test)\n\nSubmitted at Block #42\n\n" + "#### [2👍](/r/leon/hof$help&func=Upvote&pkgpath=gno.land/r/demo/test) - " + "[1👎](/r/leon/hof$help&func=Downvote&pkgpath=gno.land/r/demo/test)\n\n" From 6912719eeaa85c8b06fdb76582f84ec310269232 Mon Sep 17 00:00:00 2001 From: jeronimoalbi Date: Wed, 11 Dec 2024 17:38:31 +0100 Subject: [PATCH 6/6] chore: add interface check for record implementation --- examples/gno.land/r/leon/hof/datasource_test.gno | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/r/leon/hof/datasource_test.gno b/examples/gno.land/r/leon/hof/datasource_test.gno index ad605db32f0..376f981875f 100644 --- a/examples/gno.land/r/leon/hof/datasource_test.gno +++ b/examples/gno.land/r/leon/hof/datasource_test.gno @@ -10,9 +10,10 @@ import ( ) var ( - _ datasource.Datasource = (*Datasource)(nil) - _ datasource.Record = (*record)(nil) - _ datasource.Iterator = (*iterator)(nil) + _ datasource.Datasource = (*Datasource)(nil) + _ datasource.Record = (*record)(nil) + _ datasource.ContentRecord = (*record)(nil) + _ datasource.Iterator = (*iterator)(nil) ) func TestDatasourceRecords(t *testing.T) {