aboutsummaryrefslogtreecommitdiff
path: root/internal/types/operation.go
blob: 41bb666233b62b0be39ff21dae74cf472b024772 (plain) (blame)
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
package types

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
)

// ExportRecord mirrors a line/event from plc.directory export stream.
type ExportRecord struct {
	Seq       uint64          `json:"seq"`
	DID       string          `json:"did"`
	CreatedAt string          `json:"createdAt,omitempty"`
	CID       string          `json:"cid,omitempty"`
	Nullified bool            `json:"nullified,omitempty"`
	Operation json.RawMessage `json:"operation"`
}

type ParsedOperation struct {
	DID            string
	CanonicalBytes []byte
	Payload        map[string]any
	Sequence       uint64
	CID            string
	Prev           string
	RawRecord      ExportRecord
}

func CanonicalizeJSON(raw []byte) ([]byte, error) {
	trimmed := bytes.TrimSpace(raw)

	if len(trimmed) == 0 {
		return nil, fmt.Errorf("empty json payload")
	}

	if !json.Valid(trimmed) {
		return nil, fmt.Errorf("invalid json payload")
	}

	var out bytes.Buffer

	if err := json.Compact(&out, trimmed); err != nil {
		return nil, fmt.Errorf("compact json: %w", err)
	}

	return out.Bytes(), nil
}

func ParseOperation(rec ExportRecord) (ParsedOperation, error) {
	if rec.DID == "" {
		return ParsedOperation{}, fmt.Errorf("missing DID")
	}

	canonical, err := CanonicalizeJSON(rec.Operation)

	if err != nil {
		return ParsedOperation{}, err
	}

	payload := map[string]any{}

	if err := json.Unmarshal(canonical, &payload); err != nil {
		return ParsedOperation{}, fmt.Errorf("decode operation: %w", err)
	}

	prev, _ := payload["prev"].(string)
	cid := rec.CID

	if cid == "" {
		cid = ComputeDigestCID(canonical)
	}

	return ParsedOperation{
		DID:            rec.DID,
		CanonicalBytes: canonical,
		Payload:        payload,
		Sequence:       rec.Seq,
		CID:            cid,
		Prev:           prev,
		RawRecord:      rec,
	}, nil
}

func ComputeDigestCID(payload []byte) string {
	sum := sha256.Sum256(payload)

	return "sha256:" + hex.EncodeToString(sum[:])
}