impl.gno
1.83 Kb · 70 lines
1// Package impl is version 1 of the handler realm of the "API as data" upgrade
2// pattern (pattern G; see ../../README.md).
3//
4// Three verbs: it ADDS one and keeps both of v0's untouched, which is what
5// lets the facade accept it. Growing the API costs no new realm and no new
6// signature at the permanent path.
7package impl
8
9import (
10 "strings"
11
12 facade "gno.land/r/moul/x/upgrade/schema/facade/v0"
13)
14
15// schema is this version's whole API. One verb per line, name then parameters.
16const schema = `upper s
17repeat s n
18surround s left right`
19
20type handler struct{}
21
22func (handler) Schema() string { return schema }
23
24func (handler) Invoke(verb string, args []string) string {
25 switch verb {
26 case "upper":
27 return strings.ToUpper(args[0])
28 case "repeat":
29 return strings.Repeat(args[0], atoi(args[1]))
30 case "surround":
31 return args[1] + args[0] + args[2]
32 }
33 // Unreachable: the facade checked the verb against this schema first.
34 panic("unknown verb " + verb)
35}
36
37// atoi is hand-rolled to keep the failure message about the ARGUMENT rather
38// than about strconv, which is the whole difference a typed boundary would have
39// made here.
40func atoi(s string) int {
41 if s == "" {
42 panic("expected a number, got an empty argument")
43 }
44 n := 0
45 for _, c := range s {
46 if c < '0' || c > '9' {
47 panic("expected a number, got " + s)
48 }
49 n = n*10 + int(c-'0')
50 if n > 64 {
51 panic("refusing to repeat more than 64 times")
52 }
53 }
54 return n
55}
56
57// Instance exposes the singleton.
58func Instance() facade.Handler { return handler{} }
59
60// Path is this realm's own package path, the string the facade owner accepts.
61const Path = "gno.land/r/moul/x/upgrade/schema/impl/v1"
62
63func init(cur realm) {
64 facade.Propose(cross(cur), Instance())
65}
66
67// Propose re-nominates this handler, so a rollback needs no redeploy.
68func Propose(cur realm) {
69 facade.Propose(cross(cur), Instance())
70}