1package brokerwire
2
3// Preview wire converters (broker preview slice 1).
4//
5// StartPreview/CancelPreview travel client-to-server; PreviewPublication
6// travels server-to-client with exactly one of its preview/error result
7// members set. Conversion mirrors the port preview types losslessly and
8// validates every narrowing numeric cast before it happens, following the
9// shared converter patterns in convert.go: wire uint32 taxonomy fields
10// never truncate into a smaller semantic enum, identity lengths are exact,
11// and semantic validation failures map to ErrInvalidMessage.
12//
13// The preview viewport (RemotePreviewRequest/RemotePreview) reuses the
14// terminal wire shapes owned by the session conversation; the broker
15// conversation carries them as nested messages under its own envelope
16// tags. Repeated authority is scope/generation plus the preview target
17// identity response; the route names the observed daemon only, and the
18// broker allocates the observation stream itself.
19//
20// The field-by-field RemotePreviewRequest/RemotePreview conversion logic
21// lives in internal/adapters/protoconv, the canonical implementation shared
22// with sessionwire. The wrappers below adapt protoconv's shared
23// ErrOutOfRange to this package's errConvertRange, keeping range failures
24// distinct from a semantic protocol validation failure (which maps to
25// ErrInvalidMessage, or ErrTooLarge for an oversized preview) exactly as
26// before extraction.
27
28import (
29 "errors"
30
31 "github.com/bnema/vev/internal/adapters/protoconv"
32 "github.com/bnema/vev/internal/domain"
33 "github.com/bnema/vev/internal/ports"
34 "github.com/bnema/vev/internal/protocol"
35 "github.com/bnema/vev/internal/protocol/wire"
36)
37
38func remotePreviewRequestToWire(message protocol.RemotePreviewRequest) (*wire.RemotePreviewRequest, error) {
39 out, err := protoconv.RemotePreviewRequestToWire(message)
40 if err != nil {
41 return nil, ErrInvalidMessage
42 }
43 return out, nil
44}
45
46func remotePreviewRequestFromWire(message *wire.RemotePreviewRequest) (protocol.RemotePreviewRequest, error) {
47 if message == nil {
48 return protocol.RemotePreviewRequest{}, ErrInvalidMessage
49 }
50 request, err := protoconv.RemotePreviewRequestFromWire(message)
51 if err != nil {
52 if errors.Is(err, protoconv.ErrOutOfRange) {
53 return protocol.RemotePreviewRequest{}, errConvertRange
54 }
55 return protocol.RemotePreviewRequest{}, ErrInvalidMessage
56 }
57 return request, nil
58}
59
60func remotePreviewToWire(message protocol.RemotePreview) (*wire.RemotePreview, error) {
61 out, err := protoconv.RemotePreviewToWire(message)
62 if err != nil {
63 if errors.Is(err, protocol.ErrRemotePreviewTooLarge) {
64 return nil, ErrTooLarge
65 }
66 if errors.Is(err, protoconv.ErrOutOfRange) {
67 return nil, errConvertRange
68 }
69 return nil, ErrInvalidMessage
70 }
71 return out, nil
72}
73
74func remotePreviewFromWire(message *wire.RemotePreview) (protocol.RemotePreview, error) {
75 if message == nil {
76 return protocol.RemotePreview{}, ErrInvalidMessage
77 }
78 preview, err := protoconv.RemotePreviewFromWire(message)
79 if err != nil {
80 if errors.Is(err, protocol.ErrRemotePreviewTooLarge) {
81 return protocol.RemotePreview{}, ErrTooLarge
82 }
83 if errors.Is(err, protoconv.ErrOutOfRange) {
84 return protocol.RemotePreview{}, errConvertRange
85 }
86 return protocol.RemotePreview{}, ErrInvalidMessage
87 }
88 return preview, nil
89}
90
91// previewRequest mirrors one StartPreview as the ports request the preview
92// contract gates, so encode and decode refuse exactly what
93// ports.BrokerPreviewRequest.Validate refuses.
94func previewRequest(m StartPreview) ports.BrokerPreviewRequest {
95 return ports.BrokerPreviewRequest{Epoch: m.Epoch, Connection: m.Connection, Generation: m.Generation, Route: m.Route, Preview: m.Preview}
96}
97
98func previewRouteToWire(route ports.BrokerPreviewRoute) *wire.PreviewRoute {
99 out := &wire.PreviewRoute{Local: route.Local, Endpoint: route.Endpoint, Policy: policyToWire(route.Policy)}
100 if !route.Local {
101 out.Registration = registrationToWire(route.Registration)
102 }
103 return out
104}
105
106func previewRouteFromWire(message *wire.PreviewRoute) (ports.BrokerPreviewRoute, error) {
107 if message == nil {
108 return ports.BrokerPreviewRoute{}, ErrInvalidMessage
109 }
110 policy, err := policyFromWire(message.GetPolicy())
111 if err != nil {
112 return ports.BrokerPreviewRoute{}, ErrInvalidMessage
113 }
114 route := ports.BrokerPreviewRoute{Local: message.GetLocal(), Endpoint: message.GetEndpoint(), Policy: policy}
115 if route.Local {
116 if message.GetRegistration() != nil {
117 return ports.BrokerPreviewRoute{}, ErrInvalidMessage
118 }
119 } else if route.Registration, err = registrationFromWire(message.GetRegistration()); err != nil {
120 return ports.BrokerPreviewRoute{}, ErrInvalidMessage
121 }
122 if route.Validate() != nil {
123 return ports.BrokerPreviewRoute{}, ErrInvalidMessage
124 }
125 return route, nil
126}
127
128func startPreviewToWire(m StartPreview) (*wire.StartPreview, error) {
129 preview, err := remotePreviewRequestToWire(m.Preview)
130 if err != nil {
131 return nil, err
132 }
133 if err := previewRequest(m).Validate(); err != nil {
134 return nil, ErrInvalidMessage
135 }
136 return &wire.StartPreview{
137 Scope: scopeToWire(m.Epoch, m.Connection),
138 Generation: uint64(m.Generation),
139 Route: previewRouteToWire(m.Route),
140 Preview: preview,
141 }, nil
142}
143
144func startPreviewFromWire(message *wire.StartPreview) (StartPreview, error) {
145 if message == nil {
146 return StartPreview{}, ErrInvalidMessage
147 }
148 epoch, connection, err := scopeFromWire(message.GetScope())
149 if err != nil {
150 return StartPreview{}, ErrInvalidMessage
151 }
152 route, err := previewRouteFromWire(message.GetRoute())
153 if err != nil {
154 return StartPreview{}, err
155 }
156 preview, err := remotePreviewRequestFromWire(message.GetPreview())
157 if err != nil {
158 return StartPreview{}, err
159 }
160 candidate := StartPreview{Epoch: epoch, Connection: connection, Generation: ports.BrokerPreviewGeneration(message.GetGeneration()), Route: route, Preview: preview}
161 if err := previewRequest(candidate).Validate(); err != nil {
162 return StartPreview{}, ErrInvalidMessage
163 }
164 return candidate, nil
165}
166
167func cancelPreviewToWire(m CancelPreview) (*wire.CancelPreview, error) {
168 if m.Epoch == 0 {
169 return nil, ErrInvalidMessage
170 }
171 if err := m.Connection.Validate(); err != nil {
172 return nil, ErrInvalidMessage
173 }
174 if err := m.Generation.Validate(); err != nil {
175 return nil, ErrInvalidMessage
176 }
177 return &wire.CancelPreview{
178 Scope: scopeToWire(m.Epoch, m.Connection),
179 Generation: uint64(m.Generation),
180 }, nil
181}
182
183func cancelPreviewFromWire(message *wire.CancelPreview) (CancelPreview, error) {
184 var out CancelPreview
185 if message == nil {
186 return out, ErrInvalidMessage
187 }
188 epoch, connection, err := scopeFromWire(message.GetScope())
189 if err != nil {
190 return CancelPreview{}, ErrInvalidMessage
191 }
192 generation := ports.BrokerPreviewGeneration(message.GetGeneration())
193 if err := generation.Validate(); err != nil {
194 return CancelPreview{}, ErrInvalidMessage
195 }
196 return CancelPreview{Epoch: epoch, Connection: connection, Generation: generation}, nil
197}
198
199func previewPublicationToWire(m PreviewPublication) (*wire.PreviewPublication, error) {
200 if m.Epoch == 0 {
201 return nil, ErrInvalidMessage
202 }
203 if err := m.Connection.Validate(); err != nil {
204 return nil, ErrInvalidMessage
205 }
206 if err := m.Generation.Validate(); err != nil {
207 return nil, ErrInvalidMessage
208 }
209 out := &wire.PreviewPublication{
210 Scope: scopeToWire(m.Epoch, m.Connection),
211 Generation: uint64(m.Generation),
212 }
213 // Exactly one of preview/error travels: a failed publication carries
214 // only its typed error, a successful one only its preview.
215 if m.HasError {
216 if err := m.Error.validate(); err != nil {
217 return nil, err
218 }
219 if previewCarriesViewport(m.Preview) {
220 return nil, ErrInvalidMessage
221 }
222 out.Result = &wire.PreviewPublication_Error{Error: errorDetailToWire(m.Error)}
223 return out, nil
224 }
225 if m.Error != (ErrorDetail{}) {
226 return nil, ErrInvalidMessage
227 }
228 preview, err := remotePreviewToWire(m.Preview)
229 if err != nil {
230 return nil, err
231 }
232 out.Result = &wire.PreviewPublication_Preview{Preview: preview}
233 return out, nil
234}
235
236// previewCarriesViewport reports whether a preview value carries any
237// viewport authority. A failed publication must carry none.
238func previewCarriesViewport(preview protocol.RemotePreview) bool {
239 return preview.Version != 0 || preview.Status != 0 ||
240 preview.LifecycleID != (domain.SessionLifecycleID{}) || preview.TabID != "" ||
241 preview.Revision != 0 || preview.Width != 0 || preview.Height != 0 ||
242 len(preview.Cells) != 0
243}
244
245func previewPublicationFromWire(message *wire.PreviewPublication) (PreviewPublication, error) {
246 var out PreviewPublication
247 if message == nil {
248 return out, ErrInvalidMessage
249 }
250 epoch, connection, err := scopeFromWire(message.GetScope())
251 if err != nil {
252 return PreviewPublication{}, ErrInvalidMessage
253 }
254 generation := ports.BrokerPreviewGeneration(message.GetGeneration())
255 if err := generation.Validate(); err != nil {
256 return PreviewPublication{}, ErrInvalidMessage
257 }
258 result := PreviewPublication{Epoch: epoch, Connection: connection, Generation: generation}
259 switch payload := message.GetResult().(type) {
260 case *wire.PreviewPublication_Preview:
261 if payload.Preview == nil {
262 return PreviewPublication{}, ErrInvalidMessage
263 }
264 preview, err := remotePreviewFromWire(payload.Preview)
265 if err != nil {
266 return PreviewPublication{}, err
267 }
268 result.Preview = preview
269 case *wire.PreviewPublication_Error:
270 if payload.Error == nil {
271 return PreviewPublication{}, ErrInvalidMessage
272 }
273 detail, err := errorDetailFromWire(payload.Error)
274 if err != nil {
275 return PreviewPublication{}, ErrInvalidMessage
276 }
277 result.Error = detail
278 result.HasError = true
279 default:
280 // An absent result is malformed: exactly one member is required.
281 return PreviewPublication{}, ErrInvalidMessage
282 }
283 return result, nil
284}