-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathcanon.go
83 lines (73 loc) · 2.16 KB
/
canon.go
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
package coze
import (
"bytes"
"encoding/json"
)
// Canon returns the current canon from raw JSON.
//
// It returns only top level fields with no recursion or promotion of embedded
// fields.
func Canon(raw json.RawMessage) (can []string, err error) {
o := newOrderedMap()
err = json.Unmarshal(raw, &o)
if err != nil {
return nil, err
}
return o.Keys(), nil
}
// Canonical returns the canonical form. Input canon is optional and may be nil.
// If canon is nil, input JSON is only compactified.
//
// Interface "canon" may be `[]string`, `struct“, or `nil`. If "canon" is a
// struct or slice it must be properly ordered. If canon is nil, json.Unmarshal
// will place the input into a UTF-8 ordered map.
//
// In the Go version of Coze, the canonical form of a struct is (currently)
// achieved by unmarshalling and remarshalling.
func Canonical(input []byte, canon any) (b []byte, err error) {
if canon == nil {
return compact(input)
}
s, ok := canon.([]string)
if ok {
// The only datastructure that can unmarshal arbitrary JSON is map, but
// json.Marshal will unmarshal *all* elements and there is no way to specify
// unmarshalling to only the given fields. Solution: unmarshal into new
// map, and transfer appropriate fields to a second map.
m := make(map[string]any)
err = json.Unmarshal(input, &m)
if err != nil {
return nil, err
}
mm := make(map[string]any)
for i := 0; i < len(s); i++ {
mm[s[i]] = m[s[i]]
}
return Marshal(mm)
}
// 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 HshAlg) (digest B64, err error) {
input, err = Canonical(input, canon)
if err != nil {
return nil, err
}
return Hash(hash, input)
}
// Compact is a helper that compactifies JSON.
func compact(msg json.RawMessage) ([]byte, error) {
var b bytes.Buffer
err := json.Compact(&b, msg)
if err != nil {
return nil, err
}
return b.Bytes(), nil
}