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

submit.gno

3.01 Kb · 130 lines
  1package test4
  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}