forked from Cyphrme/Coz
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnormal.go
More file actions
259 lines (242 loc) · 6.33 KB
/
normal.go
File metadata and controls
259 lines (242 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
package coze
import (
"bytes"
"encoding/json"
"fmt"
"sort"
)
// Normal - All normals are arrays of fields. In Go this is implemented at a
// slice []string.
//
// canon (can)
// only (ony)
// need (ned)
// order (ord)
// option (opt)
//
// `canon` requires specified fields in the given order and no extra fields
// permitted.
//
// `only` specifies fields that are required to be present, does not specify any
// order, and no extra fields permitted.
//
// `need` specifies fields that are required to be present, but does not specify
// any order. Additional fields are permitted and `need` may be used with an
// `option`.
//
// `order` requires specified fields in the given order and additional fields
// are permitted after the order fields. An `order` may be used with an
// `option`.
//
// `option` specifies permissible optional fields and may be used alone or used
// with `need` or `order`. Extra fields are not allowed regardless if `option`
// is used alone or with a `need` or `order`.
//
// ## Normal, Require, and Option
//
// `canon`, `only`, `need`, and `order` are valid `require` in that they specify
// required fields. An option is distinct in that option specifies optional
// fields and precludes other optional fields.
//
// ┌────────────────┐
// │ Normal │
// └───────┬────────┘
// ┌───────┴────────┐
// ┌─────┴────┐ ┌─────┴──────┐
// │ Require │ │ Option │
// └──────────┘ └────────────┘
// Normal Hierarchy
// Normal
// Require
// canon
// only
// need
// order
// Option
// option
//
// Venn Diagram of Normal - Require "mixing" with Option
//
// Require | Both | Option
// ┌───────┬──────┬──────┐
// │ │ │ │
// │ Canon │ Need │Option│
// │ Only │ Order│ │
// │ │ │ │
// └───────┴──────┴──────┘
//
type Normal []string
type (
Canon Normal
Only Normal
Need Normal
Order Normal
Option Normal
)
// IsNormal checks if a Coze is normalized. See notes on Normal. Param opt may
// be nil. If opt is considered invalid for Canon and Only and if opt is set
// for either type function returns false. If opt is not nil for Need or Order,
// no extra fields are allowed outside of what's specified by norm plus opt. If
// opt is nil, all extra fields are valid.
//
// Note that parameter norm must be typed as Canon, Only, Need, Order, or
// Option. (TODO probably type norm as Normal. There appears to be some Go
// issues typing this)
//
// Repeated keys between opt and norm is allowed.
func IsNormal(pay json.RawMessage, norm any, opt Option) bool {
ms := MapSlice{}
err := json.Unmarshal(pay, &ms)
if err != nil {
return false
}
if opt != nil {
sort.Strings(opt)
// Optional only.
if norm == nil {
norm = opt
}
}
switch v := norm.(type) {
case Canon:
if len(v) != len(ms) {
return false
}
for i, mi := range ms {
if mi.Key != v[i] {
return false
}
}
case Only:
if len(v) != len(ms) {
return false
}
keys := ms.Keys()
sort.Strings(keys)
sort.Strings(v)
for i := range v {
if v[i] != keys[i] {
return false
}
}
case Need:
keys := ms.Keys()
sort.Strings(keys)
sort.Strings(v)
matches := 0
optMatches := 0
for _, value := range keys {
if matches == len(v) || v[matches] != value {
if opt != nil {
if optMatches == len(opt) || opt[optMatches] != value {
return false
}
optMatches++
}
continue
} else {
matches++
}
}
// Bookends
if matches != len(v) {
return false
}
if opt != nil && optMatches+matches != len(keys) {
return false
}
case Order:
i := 0
value := ""
keys := ms.Keys()
if len(v) > len(keys) {
return false
}
for i, value = range v {
if value != keys[i] {
return false
}
}
if opt != nil {
after := keys[i+1:]
sort.Strings(opt)
for _, value = range after {
if !contains(opt, value) {
return false
}
}
}
case Option:
if opt != nil {
v = append(v, opt...) // merge
}
sort.Strings(v)
keys := ms.Keys()
for _, value := range keys {
if !contains(v, value) {
return false
}
}
default:
return false
}
return true
}
func contains(s []string, search string) bool {
i := sort.SearchStrings(s, search)
return i < len(s) && s[i] == search
}
// Canon returns the current canon from raw JSON.
//
// It returns only top level fields with no recursion or promotion of embedded
// fields.
func GetCanon(raw json.RawMessage) (can []string, err error) {
ms := MapSlice{}
err = json.Unmarshal(raw, &ms)
if err != nil {
return nil, err
}
keys := make([]string, len(ms))
for i, v := range ms {
keys[i] = fmt.Sprintf("%v", v.Key)
}
return keys, nil
}
// Canonical returns the canonical form. Input canon may be nil. If canon is
// nil, JSON is only compactified.
//
// Interface "canon" may be any valid type for json.Unmarshal, including
// `[]string`, `struct``, and `nil`. If canon is nil, json.Unmarshal will place
// the input into a UTF-8 ordered map.
//
// If "canon" is a struct the struct must be properly ordered. Go's JSON package
// orders struct fields according to their struct position.
//
// In the Go version of Coze, the canonical form of a struct is (currently)
// achieved by unmarshalling and remarshaling.
func Canonical(input []byte, canon any) (b []byte, err error) {
if canon == nil { // only compactify
var b bytes.Buffer
err = json.Compact(&b, input)
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
// Unmarshal the given bytes into the given canonical format.
err = json.Unmarshal(input, &canon)
if err != nil {
return nil, err
}
return Marshal(canon)
}
// CanonicalHash accepts []byte and optional canon and returns digest.
//
// If input is already in canonical form, Hash() may also be called instead.
func CanonicalHash(input []byte, canon any, hash HashAlg) (digest B64, err error) {
input, err = Canonical(input, canon)
if err != nil {
return nil, err
}
return Hash(hash, input), nil
}