carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package provision
2
3import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "io"
8 "strings"
9
10 "github.com/carlosframework/platform/internal/maintenanceguard"
11)
12
13var ErrMaintenanceProjection = errors.New("provision: invalid maintenance projection or protected fields")
14
15// MaintenanceProjection retains a present projection, including invalid input.
16// Its value receiver validates on marshal, so an ignored decode error cannot
17// turn a present invalid projection into an absent one. An empty slice is absent.
18type MaintenanceProjection []byte
19
20func (p *MaintenanceProjection) UnmarshalJSON(raw []byte) error {
21 *p = append((*p)[:0], raw...)
22 _, err := maintenanceguard.DecodeProjection(raw)
23 if err != nil {
24 return ErrMaintenanceProjection
25 }
26 return nil
27}
28func (p MaintenanceProjection) MarshalJSON() ([]byte, error) {
29 if _, err := maintenanceguard.DecodeProjection(p); err != nil {
30 return nil, ErrMaintenanceProjection
31 }
32 return append([]byte(nil), p...), nil
33}
34
35// DecodeRecord is the bucket-record parser. API response views may use ordinary
36// JSON decoding; they do not authorize writes. Keep this off Record's method
37// set so embedding Record cannot swallow a wrapper's fields or Gate shadow.
38func DecodeRecord(raw []byte) (Record, error) {
39 invalid := Record{Maintenance: MaintenanceProjection("null")}
40 dec := json.NewDecoder(bytes.NewReader(raw))
41 first, e := dec.Token()
42 if e != nil || first != json.Delim('{') {
43 return invalid, ErrMaintenanceProjection
44 }
45 seen := false
46 for dec.More() {
47 key, e := dec.Token()
48 if e != nil {
49 return invalid, ErrMaintenanceProjection
50 }
51 name, ok := key.(string)
52 if !ok {
53 return invalid, ErrMaintenanceProjection
54 }
55 if strings.EqualFold(name, "maintenance") {
56 if name != "maintenance" || seen {
57 return invalid, ErrMaintenanceProjection
58 }
59 seen = true
60 }
61 var value json.RawMessage
62 if dec.Decode(&value) != nil {
63 return invalid, ErrMaintenanceProjection
64 }
65 }
66 if end, e := dec.Token(); e != nil || end != json.Delim('}') {
67 return invalid, ErrMaintenanceProjection
68 }
69 if _, e = dec.Token(); e != io.EOF {
70 return invalid, ErrMaintenanceProjection
71 }
72 var rec Record
73 if json.Unmarshal(raw, &rec) != nil {
74 return invalid, ErrMaintenanceProjection
75 }
76 return rec, nil
77}
78
79// Equality preserves the projection, not permission to change a fenced record.
80// Durable guard and lifecycle checks belong to the maintenance integration.
81func protectedFieldsEqual(before, after Record) bool {
82 if before.RecordSeq != after.RecordSeq || before.Account != after.Account || before.App != after.App || before.Host != after.Host || !before.CreatedAt.Equal(after.CreatedAt) {
83 return false
84 }
85 if len(before.Maintenance) == 0 || len(after.Maintenance) == 0 {
86 return len(before.Maintenance) == len(after.Maintenance)
87 }
88 a, e := maintenanceguard.DecodeProjection(before.Maintenance)
89 if e != nil {
90 return false
91 }
92 b, e := maintenanceguard.DecodeProjection(after.Maintenance)
93 return e == nil && a == b
94}
95