# 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](https://github.com/jeronimoalbi/gnome),
as part of `jeronimoalbi`'s Gno smart contracts monorepo.
## Usage
[embedmd]:# (filetests/readme_filetest.gno go)
```go
package main
import (
"errors"
"strings"
"gno.land/p/g17khqpukees4237dtn3astzapmp462vjhsz6st4/datasource"
)
func main() {
var ds datasource.Datasource = posts{
{"1", "Hello Gno", []string{"gno"}},
{"2", "Realms 101", []string{"gno", "realms"}},
{"3", "Cooking pasta", []string{"food"}},
{"4", "Testing in Gno", []string{"gno", "testing"}},
}
// Get the first two records tagged with "gno"
records, err := datasource.QueryRecords(
ds,
datasource.ByTag("gno"),
datasource.WithCount(2),
)
if err != nil {
panic(err)
}
for _, r := range records {
println(r.ID(), r.String())
}
// Get the next page of records tagged with "gno"
records, err = datasource.QueryRecords(
ds,
datasource.ByTag("gno"),
datasource.WithOffset(2),
datasource.WithCount(2),
)
if err != nil {
panic(err)
}
for _, r := range records {
println(r.ID(), r.String())
}
// Get a single record, and use its optional interfaces
r, err := ds.Record("2")
if err != nil {
panic(err)
}
if t, ok := r.(datasource.TaggableRecord); ok {
println("Tags:", strings.Join(t.Tags(), ", "))
}
fields, err := r.Fields()
if err != nil {
panic(err)
}
title, _ := fields.Get("title")
println("Title:", title.(string))
}
// Datasource: A list of posts
type posts []post
func (ps posts) Size() int { return len(ps) }
func (ps posts) Record(id string) (datasource.Record, error) {
for _, p := range ps {
if p.id == id {
return p, nil
}
}
return nil, errors.New("record not found")
}
func (ps posts) Records(q datasource.Query) datasource.Iterator {
var records []datasource.Record
for _, p := range ps {
if q.Tag == "" || p.hasTag(q.Tag) {
records = append(records, p)
}
}
if q.Offset >= len(records) {
return datasource.NewRecordIterator(nil)
}
return datasource.NewRecordIterator(records[q.Offset:])
}
// TaggableRecord: A post
type post struct {
id string
title string
tags []string
}
func (p post) ID() string { return p.id }
func (p post) String() string { return p.title }
func (p post) Tags() []string { return p.tags }
func (p post) Fields() (datasource.Fields, error) {
return datasource.NewFieldsFromMap(map[string]any{
"title": p.title,
}), nil
}
func (p post) hasTag(tag string) bool {
for _, t := range p.tags {
if t == tag {
return true
}
}
return false
}
// Output:
// 1 Hello Gno
// 2 Realms 101
// 4 Testing in Gno
// Tags: gno, realms
// Title: Realms 101
```
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.