core.gno
4.41 Kb · 148 lines
1// Package core holds the shared Remix Lab contracts: error categories, roles,
2// inert view records and the small read interfaces that blocks and adapters
3// implement.
4//
5// Nothing in this package holds state. View records are plain values that
6// never alias a block's internal storage, so returning them from a realm does
7// not hand out mutation capability.
8package core
9
10import (
11 "errors"
12 "strings"
13 "unicode/utf8"
14)
15
16// ViewSchemaVersion is the version of the view/action shapes defined here.
17// Bump it whenever an exported view struct changes shape.
18const ViewSchemaVersion = 1
19
20// Error categories. Blocks wrap these so that clients can classify failures
21// by prefix without parsing free-form text.
22var (
23 ErrUnauthorized = errors.New("remix: unauthorized")
24 ErrNotFound = errors.New("remix: not found")
25 ErrInvalidInput = errors.New("remix: invalid input")
26 ErrLimitExceeded = errors.New("remix: limit exceeded")
27 ErrInvalidState = errors.New("remix: invalid state")
28 ErrConflict = errors.New("remix: conflict")
29)
30
31// Errorf returns an error whose message starts with the category's message,
32// e.g. "remix: invalid input: title is empty".
33func Errorf(category error, detail string) error {
34 return errors.New(category.Error() + ": " + detail)
35}
36
37// Is reports whether err belongs to category.
38func Is(err, category error) bool {
39 if err == nil || category == nil {
40 return false
41 }
42 msg := err.Error()
43 cat := category.Error()
44 return msg == cat || strings.HasPrefix(msg, cat+": ")
45}
46
47// Role is an address's standing inside a membership set. Roles are ordered:
48// a higher role satisfies any requirement for a lower one.
49type Role int
50
51const (
52 RoleNone Role = iota
53 RoleMember
54 RoleReviewer
55 RoleOwner
56)
57
58func (r Role) String() string {
59 switch r {
60 case RoleNone:
61 return "none"
62 case RoleMember:
63 return "member"
64 case RoleReviewer:
65 return "reviewer"
66 case RoleOwner:
67 return "owner"
68 }
69 return "unknown"
70}
71
72// Valid reports whether r is one of the defined roles.
73func (r Role) Valid() bool {
74 return r >= RoleNone && r <= RoleOwner
75}
76
77// AtLeast reports whether r satisfies a requirement of min.
78func (r Role) AtLeast(min Role) bool {
79 return r >= min
80}
81
82// ParseRole converts the canonical role name to a Role.
83func ParseRole(s string) (Role, error) {
84 switch s {
85 case "none":
86 return RoleNone, nil
87 case "member":
88 return RoleMember, nil
89 case "reviewer":
90 return RoleReviewer, nil
91 case "owner":
92 return RoleOwner, nil
93 }
94 return RoleNone, Errorf(ErrInvalidInput, "unknown role")
95}
96
97// CheckText validates user-supplied text: valid UTF-8, between minBytes and
98// maxBytes long, not blank when minBytes > 0, and free of control
99// characters (other than newline when multiline is set) and of invisible or
100// direction-changing format characters (see IsHiddenFormat). It does not
101// escape; rendering is the render block's job.
102func CheckText(field, s string, minBytes, maxBytes int, multiline bool) error {
103 if len(s) < minBytes || (minBytes > 0 && strings.TrimSpace(s) == "") {
104 if minBytes == 1 || len(s) >= minBytes {
105 return Errorf(ErrInvalidInput, field+" is required")
106 }
107 return Errorf(ErrInvalidInput, field+" is too short")
108 }
109 if len(s) > maxBytes {
110 return Errorf(ErrLimitExceeded, field+" is too long")
111 }
112 if !utf8.ValidString(s) {
113 return Errorf(ErrInvalidInput, field+" is not valid UTF-8")
114 }
115 for _, r := range s {
116 if r == '\n' && multiline {
117 continue
118 }
119 if r < 0x20 || r == 0x7f || (r >= 0x80 && r < 0xa0) || r == 0x2028 || r == 0x2029 {
120 return Errorf(ErrInvalidInput, field+" contains control characters")
121 }
122 if IsHiddenFormat(r) {
123 return Errorf(ErrInvalidInput, field+" contains invisible or direction-changing characters")
124 }
125 }
126 return nil
127}
128
129// IsHiddenFormat reports whether r is an invisible or direction-changing
130// format character that could make text display differently from its
131// content: soft hyphen, Arabic letter mark, Mongolian vowel separator,
132// zero-width space, LRM/RLM, bidi embeddings, overrides and isolates,
133// invisible operators, the byte order mark and interlinear annotations.
134// ZWNJ and ZWJ (U+200C, U+200D) are allowed because several scripts and
135// emoji sequences need them.
136func IsHiddenFormat(r rune) bool {
137 switch {
138 case r == 0x00ad, r == 0x061c, r == 0x180e, r == 0x200b, r == 0x200e, r == 0x200f, r == 0xfeff:
139 return true
140 case r >= 0x202a && r <= 0x202e:
141 return true
142 case r >= 0x2060 && r <= 0x206f:
143 return true
144 case r >= 0xfff9 && r <= 0xfffb:
145 return true
146 }
147 return false
148}