Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: datasource for gno.land/r/leon/hof integration with Gno.me #3247

Merged
merged 10 commits into from
Dec 16, 2024
77 changes: 77 additions & 0 deletions examples/gno.land/r/leon/hof/datasource.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package hof

import (
"errors"

"gno.land/p/demo/avl"
"gno.land/p/demo/ufmt"
"gno.land/p/jeronimoalbi/datasource"
)

func NewDatasource() Datasource {
return Datasource{exhibition}
}

type Datasource struct {
exhibition *Exhibition
}

func (ds Datasource) Size() int { return ds.exhibition.itemsSorted.Size() }

func (ds Datasource) Records(q datasource.Query) datasource.Iterator {
return &iterator{
exhibition: ds.exhibition,
index: q.Offset,
maxIndex: q.Offset + q.Count,
}
}

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 {
item *Item
}

func (r record) ID() string { return r.item.id.String() }
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) Content() (string, error) {
content := ufmt.Sprintf("# Submission #%d\n\n", int(r.item.id))
content += r.item.Render(false)
return content, nil
}

type iterator struct {
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 >= it.maxIndex || it.index >= it.exhibition.itemsSorted.Size() {
return false
}

_, v := it.exhibition.itemsSorted.GetByIndex(it.index)
it.record = &record{v.(*Item)}
it.index++
return true
}
157 changes: 157 additions & 0 deletions examples/gno.land/r/leon/hof/datasource_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
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.ContentRecord = (*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 #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"
uassert.Equal(t, wantContent, content)
}
Loading