Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

submit.gno

3.01 Kb · 130 lines
  1package forms
  2
  3import (
  4	"chain"
  5	"chain/runtime"
  6	"strconv"
  7	"strings"
  8)
  9
 10// Submit records a response to a form. Answers a1..a8 align with the form's
 11// fields in order; slots beyond the form's field count must be empty.
 12//
 13// gnoweb builds this call from the rendered form: each input is named after
 14// the parameter it fills, and parameters with no input arrive as "".
 15//
 16// The respondent pays the storage deposit for their own answers, and gets it
 17// back on Withdraw.
 18func Submit(cur realm, id string, a1, a2, a3, a4, a5, a6, a7, a8 string) {
 19	author := mustUserCaller(cur)
 20	f := mustGetForm(id)
 21	height := runtime.ChainHeight()
 22
 23	if !f.IsOpen(height) {
 24		if f.Deadline > 0 && height >= f.Deadline {
 25			panic(ErrDeadlinePassed)
 26		}
 27		panic(ErrFormClosed)
 28	}
 29
 30	if f.OnePerAddr && f.byAddr.Has(author.String()) {
 31		panic(ErrAlreadyResponded)
 32	}
 33
 34	answers := validateAnswers(f, []string{a1, a2, a3, a4, a5, a6, a7, a8})
 35
 36	rid := nextRespID.Next()
 37	r := &Response{
 38		ID:      rid,
 39		Form:    f.ID,
 40		Author:  author,
 41		Height:  height,
 42		Answers: answers,
 43	}
 44
 45	f.responses.Set(rid.String(), r)
 46	f.byAddr.Set(author.String(), rid.String())
 47
 48	chain.Emit("ResponseSubmitted", "form", id, "response", rid.String(), "author", author.String())
 49}
 50
 51// Withdraw deletes the caller's response to a form. The storage deposit the
 52// answers were holding is refunded to the caller by the chain.
 53//
 54// Only the author can withdraw. Owners cannot delete responses: the refund
 55// goes to whoever deletes, which would hand the owner the respondent's
 56// deposit.
 57func Withdraw(cur realm, id string) {
 58	author := mustUserCaller(cur)
 59	f := mustGetForm(id)
 60
 61	raw := f.byAddr.Get(author.String())
 62	if raw == nil {
 63		panic(ErrNoResponse)
 64	}
 65	rid := raw.(string)
 66
 67	f.responses.Remove(rid)
 68	f.byAddr.Remove(author.String())
 69
 70	chain.Emit("ResponseWithdrawn", "form", id, "response", rid, "author", author.String())
 71}
 72
 73// validateAnswers checks every answer against its field and returns the
 74// trimmed answers, exactly len(f.Fields) long. Nothing is written before it
 75// returns, so a rejected submission leaves no trace.
 76func validateAnswers(f *Form, raw []string) []string {
 77	n := len(f.Fields)
 78
 79	for i := n; i < len(raw); i++ {
 80		if strings.TrimSpace(raw[i]) != "" {
 81			panic(ErrTooManyAnswers)
 82		}
 83	}
 84
 85	answers := make([]string, n)
 86	for i, field := range f.Fields {
 87		a := strings.TrimSpace(raw[i])
 88
 89		if a == "" {
 90			if field.Required {
 91				panic(fieldErr(i, ErrRequired))
 92			}
 93			answers[i] = ""
 94			continue
 95		}
 96
 97		if len(a) > field.MaxLen() {
 98			panic(fieldErr(i, ErrAnswerTooLong))
 99		}
100
101		switch field.Kind {
102		case KindNumber:
103			if _, err := strconv.ParseFloat(a, 64); err != nil {
104				panic(fieldErr(i, ErrNotANumber))
105			}
106		case KindSelect:
107			if !contains(field.Options, a) {
108				panic(fieldErr(i, ErrNotAnOption))
109			}
110		}
111
112		answers[i] = a
113	}
114
115	return answers
116}
117
118func fieldErr(i int, err error) string {
119	return "field " + strconv.Itoa(i+1) + ": " + err.Error()
120}
121
122func contains(list []string, s string) bool {
123	for _, v := range list {
124		if v == s {
125			return true
126		}
127	}
128
129	return false
130}