1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package events
17
18 import (
19 "encoding/json"
20 "errors"
21 "fmt"
22 "time"
23
24 "google.golang.org/protobuf/encoding/protojson"
25 "google.golang.org/protobuf/proto"
26 "google.golang.org/protobuf/reflect/protoreflect"
27 "google.golang.org/protobuf/types/known/anypb"
28 "google.golang.org/protobuf/types/known/timestamppb"
29
30 pb "github.com/sigstore/protobuf-specs/gen/pb-go/events/v1"
31 )
32
33
34 type EventContentType string
35
36 const (
37 ContentTypeProtobuf EventContentType = "application/protobuf"
38 ContentTypeJSON EventContentType = "application/json"
39 )
40
41
42
43 var reservedFields = map[string]struct{}{
44 "datacontenttype": {},
45 "data": {},
46 "id": {},
47 "source": {},
48 "specversion": {},
49 "type": {},
50 }
51
52
53 type Event struct {
54 ty *EventType
55 id string
56 msg protoreflect.ProtoMessage
57 attrs map[string]any
58 }
59
60
61 func (e Event) Type() *EventType {
62 return e.ty
63 }
64
65
66 func (e Event) ID() string {
67 return e.id
68 }
69
70
71
72 func (e Event) Attributes() map[string]any {
73 return e.attrs
74 }
75
76
77 func (e Event) Message() protoreflect.ProtoMessage {
78 return e.msg
79 }
80
81
82
83
84
85
86
87
88
89
90 func (t *EventType) New(id string, msg protoreflect.ProtoMessage, attributes map[string]any) (*Event, error) {
91 if id == "" {
92 return nil, errors.New("id must be set")
93 }
94 if msg == nil {
95 return nil, errors.New("msg must be set")
96 }
97 ty := msg.ProtoReflect().Descriptor().FullName()
98 if tty := t.Descriptor().FullName(); ty != tty {
99 return nil, fmt.Errorf("msg type %q does not match expected type %q", ty, tty)
100 }
101
102 for name, value := range attributes {
103 if _, ok := reservedFields[name]; ok {
104 return nil, fmt.Errorf("attribute name %q is one of the reserved CloudEvents names %v", name, reservedFields)
105 }
106 switch v := value.(type) {
107 case string, bool, int, []byte, time.Time:
108
109 default:
110 return nil, fmt.Errorf("unsupported attribute type for %q: %T", name, v)
111 }
112 }
113
114 return &Event{ty: t, id: id, msg: msg, attrs: attributes}, nil
115 }
116
117
118
119 func (e *Event) MarshalJSON() ([]byte, error) {
120 data, err := protojson.Marshal(e.msg)
121 if err != nil {
122 return nil, fmt.Errorf("marshal data to JSON: %w", err)
123 }
124
125 event := map[string]any{
126 "specversion": CloudEventsSpecVersion,
127 "id": e.ID(),
128 "source": e.Type().Source(),
129 "type": e.Type().Name(),
130 "datacontenttype": ContentTypeJSON,
131 "data": string(data),
132 }
133 for k, v := range e.attrs {
134 event[k] = v
135 }
136
137 return json.Marshal(event)
138 }
139
140
141 func (e *Event) MarshalProto() ([]byte, error) {
142 msg, err := e.Proto()
143 if err != nil {
144 return nil, fmt.Errorf("build proto: %w", err)
145 }
146 return proto.Marshal(msg)
147 }
148
149
150 func (e *Event) Proto() (*pb.CloudEvent, error) {
151 data, err := proto.Marshal(e.msg)
152 if err != nil {
153 return nil, fmt.Errorf("marshal data: %w", err)
154 }
155
156 attrs := make(map[string]*pb.CloudEvent_CloudEventAttributeValue)
157 for name, value := range e.attrs {
158 switch v := value.(type) {
159 case string:
160 attrs[name] = &pb.CloudEvent_CloudEventAttributeValue{
161 Attr: &pb.CloudEvent_CloudEventAttributeValue_CeString{CeString: v},
162 }
163 case bool:
164 attrs[name] = &pb.CloudEvent_CloudEventAttributeValue{
165 Attr: &pb.CloudEvent_CloudEventAttributeValue_CeBoolean{CeBoolean: v},
166 }
167 case int:
168 attrs[name] = &pb.CloudEvent_CloudEventAttributeValue{
169 Attr: &pb.CloudEvent_CloudEventAttributeValue_CeInteger{CeInteger: int32(v)},
170 }
171 case time.Time:
172 attrs[name] = &pb.CloudEvent_CloudEventAttributeValue{
173 Attr: &pb.CloudEvent_CloudEventAttributeValue_CeTimestamp{
174 CeTimestamp: timestamppb.New(v),
175 },
176 }
177 case []byte:
178 attrs[name] = &pb.CloudEvent_CloudEventAttributeValue{
179 Attr: &pb.CloudEvent_CloudEventAttributeValue_CeBytes{CeBytes: v},
180 }
181 }
182 }
183
184 event := &pb.CloudEvent{
185 SpecVersion: CloudEventsSpecVersion,
186 Id: e.ID(),
187 Source: e.Type().Source(),
188 Type: e.Type().Name(),
189 Attributes: attrs,
190 Data: &pb.CloudEvent_ProtoData{
191 ProtoData: &anypb.Any{
192 Value: data,
193 TypeUrl: string(e.Type().Descriptor().FullName()),
194 },
195 },
196 }
197
198 return event, nil
199 }
200
View as plain text