submit.gno
3.01 Kb · 130 lines
1package test3
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}