github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/examples/gno.land/p/demo/gnorkle/feeds/static/feed.gno (about)

     1  package static
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  
     8  	"gno.land/p/demo/gnorkle/feed"
     9  	"gno.land/p/demo/gnorkle/gnorkle"
    10  	"gno.land/p/demo/gnorkle/ingesters/single"
    11  	"gno.land/p/demo/gnorkle/message"
    12  	"gno.land/p/demo/gnorkle/storage/simple"
    13  	"gno.land/p/demo/ufmt"
    14  )
    15  
    16  // Feed is a static feed.
    17  type Feed struct {
    18  	id            string
    19  	isLocked      bool
    20  	valueDataType string
    21  	ingester      gnorkle.Ingester
    22  	storage       gnorkle.Storage
    23  	tasks         []feed.Task
    24  }
    25  
    26  // NewFeed creates a new static feed.
    27  func NewFeed(
    28  	id string,
    29  	valueDataType string,
    30  	ingester gnorkle.Ingester,
    31  	storage gnorkle.Storage,
    32  	tasks ...feed.Task,
    33  ) *Feed {
    34  	return &Feed{
    35  		id:            id,
    36  		valueDataType: valueDataType,
    37  		ingester:      ingester,
    38  		storage:       storage,
    39  		tasks:         tasks,
    40  	}
    41  }
    42  
    43  // NewSingleValueFeed is a convenience function  for creating a static feed
    44  // that autocommits a value after a single ingestion.
    45  func NewSingleValueFeed(
    46  	id string,
    47  	valueDataType string,
    48  	tasks ...feed.Task,
    49  ) *Feed {
    50  	return NewFeed(
    51  		id,
    52  		valueDataType,
    53  		&single.ValueIngester{},
    54  		simple.NewStorage(1),
    55  		tasks...,
    56  	)
    57  }
    58  
    59  // ID returns the feed's ID.
    60  func (f Feed) ID() string {
    61  	return f.id
    62  }
    63  
    64  // Type returns the feed's type.
    65  func (f Feed) Type() feed.Type {
    66  	return feed.TypeStatic
    67  }
    68  
    69  // Ingest ingests a message into the feed. It either adds the value to the ingester's
    70  // pending values or commits the value to the storage.
    71  func (f *Feed) Ingest(funcType message.FuncType, msg, providerAddress string) error {
    72  	if f == nil {
    73  		return feed.ErrUndefined
    74  	}
    75  
    76  	if f.isLocked {
    77  		return errors.New("feed locked")
    78  	}
    79  
    80  	switch funcType {
    81  	case message.FuncTypeIngest:
    82  		// Autocommit the ingester's value if it's a single value ingester
    83  		// because this is a static feed and this is the only value it will ever have.
    84  		if canAutoCommit, err := f.ingester.Ingest(msg, providerAddress); canAutoCommit && err == nil {
    85  			if err := f.ingester.CommitValue(f.storage, providerAddress); err != nil {
    86  				return err
    87  			}
    88  
    89  			f.isLocked = true
    90  		} else if err != nil {
    91  			return err
    92  		}
    93  
    94  	case message.FuncTypeCommit:
    95  		if err := f.ingester.CommitValue(f.storage, providerAddress); err != nil {
    96  			return err
    97  		}
    98  
    99  		f.isLocked = true
   100  
   101  	default:
   102  		return errors.New("invalid message function " + string(funcType))
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  // Value returns the feed's latest value, it's data type, and whether or not it can
   109  // be safely consumed. In this case it uses `f.isLocked` because, this being a static
   110  // feed, it will only ever have one value; once that value is committed the feed is locked
   111  // and there is a valid, non-empty value to consume.
   112  func (f Feed) Value() (feed.Value, string, bool) {
   113  	return f.storage.GetLatest(), f.valueDataType, f.isLocked
   114  }
   115  
   116  // MarshalJSON marshals the components of the feed that are needed for
   117  // an agent to execute tasks and send values for ingestion.
   118  func (f Feed) MarshalJSON() ([]byte, error) {
   119  	buf := new(bytes.Buffer)
   120  	w := bufio.NewWriter(buf)
   121  
   122  	w.Write([]byte(
   123  		`{"id":"` + f.id +
   124  			`","type":"` + ufmt.Sprintf("%d", int(f.Type())) +
   125  			`","value_type":"` + f.valueDataType +
   126  			`","tasks":[`),
   127  	)
   128  
   129  	first := true
   130  	for _, task := range f.tasks {
   131  		if !first {
   132  			w.WriteString(",")
   133  		}
   134  
   135  		taskJSON, err := task.MarshalJSON()
   136  		if err != nil {
   137  			return nil, err
   138  		}
   139  
   140  		w.Write(taskJSON)
   141  		first = false
   142  	}
   143  
   144  	w.Write([]byte("]}"))
   145  	w.Flush()
   146  
   147  	return buf.Bytes(), nil
   148  }
   149  
   150  // Tasks returns the feed's tasks. This allows task consumers to extract task
   151  // contents without having to marshal the entire feed.
   152  func (f Feed) Tasks() []feed.Task {
   153  	return f.tasks
   154  }
   155  
   156  // IsActive returns true if the feed is accepting ingestion requests from agents.
   157  func (f Feed) IsActive() bool {
   158  	return !f.isLocked
   159  }