Skip to content

Commit

Permalink
feat: datasource for gno.land/r/leon/hof integration with Gno.me (#…
Browse files Browse the repository at this point in the history
…3247)

This is quick initial PoC of a Gno.me integration idea.
  • Loading branch information
jeronimoalbi authored Dec 16, 2024
1 parent c48219a commit 705f424
Show file tree
Hide file tree
Showing 2 changed files with 234 additions and 0 deletions.
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)
}

0 comments on commit 705f424

Please sign in to comment.