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.

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}