/p/g17khqpukees4237dtn3astzapmp462vjhsz6st4/datasource
Datasource Package
Package defines generic interfaces for datasources. A datasource is a set of records that can be read one at a time or iterated, and that can optionally be taggable so they can be filtered by category.
Datasources are useful when the data exchanged between different realms has to
stay generic, avoiding direct dependencies between them. A realm that consumes
records only needs to know the Datasource interface, not the realm that
provides them.
The package doesn't ship a datasource implementation. It provides the contracts, a
Query type configured with the WithOffset, WithCount, ByTag and WithFilter
options, and the NewIterator and QueryRecords helpers. How each query option
is honoured is up to the datasource. QueryRecords enforces the record count
itself, so datasources only have to apply the tag, filters and offset.
Datasources that keep their records in memory can use NewRecordIterator to
return the iterator that queries expect, and NewFieldsFromMap to expose a map
as read-only record fields, instead of writing their own.
Warning
For security, any public function that accepts a Datasource as a parameter from external callers MUST verify that the received datasource is an expected canonical one.
Repository can be found at jeronimoalbi/gnome,
as part of jeronimoalbi's Gno smart contracts monorepo.
Usage
1package main
2
3import (
4 "errors"
5 "strings"
6
7 "gno.land/p/g17khqpukees4237dtn3astzapmp462vjhsz6st4/datasource"
8)
9
10func main() {
11 var ds datasource.Datasource = posts{
12 {"1", "Hello Gno", []string{"gno"}},
13 {"2", "Realms 101", []string{"gno", "realms"}},
14 {"3", "Cooking pasta", []string{"food"}},
15 {"4", "Testing in Gno", []string{"gno", "testing"}},
16 }
17
18 // Get the first two records tagged with "gno"
19 records, err := datasource.QueryRecords(
20 ds,
21 datasource.ByTag("gno"),
22 datasource.WithCount(2),
23 )
24 if err != nil {
25 panic(err)
26 }
27
28 for _, r := range records {
29 println(r.ID(), r.String())
30 }
31
32 // Get the next page of records tagged with "gno"
33 records, err = datasource.QueryRecords(
34 ds,
35 datasource.ByTag("gno"),
36 datasource.WithOffset(2),
37 datasource.WithCount(2),
38 )
39 if err != nil {
40 panic(err)
41 }
42
43 for _, r := range records {
44 println(r.ID(), r.String())
45 }
46
47 // Get a single record, and use its optional interfaces
48 r, err := ds.Record("2")
49 if err != nil {
50 panic(err)
51 }
52
53 if t, ok := r.(datasource.TaggableRecord); ok {
54 println("Tags:", strings.Join(t.Tags(), ", "))
55 }
56
57 fields, err := r.Fields()
58 if err != nil {
59 panic(err)
60 }
61
62 title, _ := fields.Get("title")
63 println("Title:", title.(string))
64}
65
66// Datasource: A list of posts
67type posts []post
68
69func (ps posts) Size() int { return len(ps) }
70
71func (ps posts) Record(id string) (datasource.Record, error) {
72 for _, p := range ps {
73 if p.id == id {
74 return p, nil
75 }
76 }
77 return nil, errors.New("record not found")
78}
79
80func (ps posts) Records(q datasource.Query) datasource.Iterator {
81 var records []datasource.Record
82 for _, p := range ps {
83 if q.Tag == "" || p.hasTag(q.Tag) {
84 records = append(records, p)
85 }
86 }
87
88 if q.Offset >= len(records) {
89 return datasource.NewRecordIterator(nil)
90 }
91 return datasource.NewRecordIterator(records[q.Offset:])
92}
93
94// TaggableRecord: A post
95type post struct {
96 id string
97 title string
98 tags []string
99}
100
101func (p post) ID() string { return p.id }
102func (p post) String() string { return p.title }
103func (p post) Tags() []string { return p.tags }
104
105func (p post) Fields() (datasource.Fields, error) {
106 return datasource.NewFieldsFromMap(map[string]any{
107 "title": p.title,
108 }), nil
109}
110
111func (p post) hasTag(tag string) bool {
112 for _, t := range p.tags {
113 if t == tag {
114 return true
115 }
116 }
117 return false
118}
119
120// Output:
121// 1 Hello Gno
122// 2 Realms 101
123// 4 Testing in Gno
124// Tags: gno, realms
125// Title: Realms 101