Transaction

49328A5723B74F…0C04238766EC

Block 168,175 · index 0 · indexed

Summary

Hash
49328A5723B74F06525479733E5E8E03EE11CDAF585D9315323B0C04238766EC
Block
168,175
Size
10225 bytes
Gas used
9,953,208 / 10,000,000
Fee
10000 ugnot
Status
success

Messages

Arguments · 15

  1. #1datasource
  2. #2README.md
  3. #3# 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 ```
  4. #4datasource.gno
  5. #5// Package datasource defines generic interfaces for datasources. // // Datasources contain a set of records which can optionally be // taggable. Tags can optionally be used to filter records by taxonomy. // // Datasources can help in cases where the data sent during // communication between different realms needs to be generic // to avoid direct dependencies. package datasource import "errors" // ErrInvalidRecord indicates that a datasource contains invalid records. var ErrInvalidRecord = errors.New("datasource records is not valid") type ( // Fields defines an interface for read-only fields. Fields interface { // Has checks whether a field exists. Has(name string) bool // Get retrieves the value associated with the given field. Get(name string) (value any, found bool) } // Record defines a datasource record. Record interface { // ID returns the unique record's identifier. ID() string // String returns a string representation of the record. String() string // Fields returns record fields and values. Fields() (Fields, error) } // TaggableRecord defines a datasource record that supports tags. // Tags can be used to build a taxonomy to filter records by category. TaggableRecord interface { // Tags returns a list of tags for the record. Tags() []string } // ContentRecord defines a datasource record that can return content. ContentRecord interface { // Content returns the record content. Content() (string, error) } // Iterator defines an iterator of datasource records. Iterator interface { // Next returns true when a new record is available. Next() bool // Err returns any error raised when reading records. Err() error // Record returns the current record. Record() Record } // Datasource defines a generic datasource. Datasource interface { // Records returns a new datasource records iterator. Records(Query) Iterator // Size returns the total number of records in the datasource. // When -1 is returned it means datasource doesn't support size. Size() int // Record returns a single datasource record. Record(id string) (Record, error) } ) // NewIterator returns a new record iterator for a datasource query. func NewIterator(ds Datasource, options ...QueryOption) Iterator { return ds.Records(NewQuery(options...)) } // QueryRecords return a slice of records for a datasource query. func QueryRecords(ds Datasource, options ...QueryOption) ([]Record, error) { var ( records []Record query = NewQuery(options...) iter = ds.Records(query) ) for i := 0; i < query.Count && iter.Next(); i++ { r := iter.Record() if r == nil { return nil, ErrInvalidRecord } records = append(records, r) } if err := iter.Err(); err != nil { return nil, err } return records, nil }
  6. #6fields.gno
  7. #7package datasource // NewFieldsFromMap returns read-only fields for a map of field names and values. // // The map is wrapped as is and not copied, so changes made to it by the caller // are also visible through the returned fields. func NewFieldsFromMap(m map[string]any) Fields { return fields{values: m} } type fields struct { values map[string]any } func (f fields) Has(name string) bool { _, found := f.values[name] return found } func (f fields) Get(name string) (value any, found bool) { value, found = f.values[name] return value, found }
  8. #8gnomod.toml
  9. #9module = "gno.land/p/g17khqpukees4237dtn3astzapmp462vjhsz6st4/datasource" gno = "0.9"
  10. #10iterator.gno
  11. #11package datasource // NewRecordIterator returns an iterator for a list of records. // The iterator never returns an error and it doesn't modify the slice. func NewRecordIterator(records []Record) Iterator { return &recordIterator{records: records} } type recordIterator struct { records []Record current Record } func (it *recordIterator) Err() error { return nil } func (it *recordIterator) Record() Record { return it.current } func (it *recordIterator) Next() bool { if len(it.records) == 0 { return false } it.current, it.records = it.records[0], it.records[1:] return true }
  12. #12query.gno
  13. #13package datasource import "gno.land/p/nt/avl/v0" // DefaultQueryRecords defines the default number of records returned by queries. const DefaultQueryRecords = 50 type ( // QueryOption configures datasource queries. QueryOption func(*Query) // Query contains datasource query options. Query struct { // Offset of the first record to return during iteration. Offset int // Count contains the number to records that query should return. Count int // Tag contains a tag to use as filter for the records. Tag string // Filters contains optional query filters by field value. Filters avl.Tree } ) // WithOffset configures query to return records starting from an offset. func WithOffset(offset int) QueryOption { return func(q *Query) { q.Offset = offset } } // WithCount configures the number of records that query returns. func WithCount(count int) QueryOption { return func(q *Query) { if count < 1 { count = DefaultQueryRecords } q.Count = count } } // ByTag configures query to filter by tag. func ByTag(tag string) QueryOption { return func(q *Query) { q.Tag = tag } } // WithFilter assigns a new filter argument to a query. // This option can be used multiple times if more than one // filter has to be given to the query. func WithFilter(field string, value any) QueryOption { return func(q *Query) { q.Filters.Set(field, value) } } // NewQuery creates a new datasource query. func NewQuery(options ...QueryOption) Query { // Construct a fresh Query in the caller's realm; copying from a // package-level default would propagate the /p/-readonly taint // and break the apply(&q) options below. q := Query{Count: DefaultQueryRecords} for _, apply := range options { apply(&q) } return q }
  14. #14/gno.MemPackageType
  15. #15 MPUserAll

Result log

msg:0,success:true,log:,events:[]