1pub mod client;
3pub mod identity_management;
4use crate::StreamingShape;
5use crate::heddle::api::common::{
6 AuthorizationAccess, AuthorizationExistence, AuthorizationRole, AuthorizationScopeSource,
7 CallContext, DeploymentTarget, RetryBehavior, RpcEffect, ServiceMaturity, SigningTier,
8};
9use crate::heddle::api::v1alpha2::{StreamDataKind, StreamFrame, stream_frame};
10
11include!(concat!(env!("OUT_DIR"), "/heddle_api_v2_methods.rs"));
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub struct AuthorizationTarget {
16 pub path: &'static str,
17 pub role: AuthorizationRole,
18}
19
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct AuthorizationPolicy {
25 pub role: AuthorizationRole,
26 pub scope_source: AuthorizationScopeSource,
27 pub existence: AuthorizationExistence,
28 pub targets: &'static [AuthorizationTarget],
29}
30
31#[derive(Debug)]
33pub struct RoutedCall<'a> {
34 pub method: &'static MethodDescriptor,
35 pub context: &'a CallContext,
36}
37
38impl<'a> RoutedCall<'a> {
39 pub fn new(path: &str, context: &'a CallContext) -> Option<Self> {
40 method_descriptor(path).map(|method| Self { method, context })
41 }
42}
43
44impl MethodDescriptor {
45 pub const fn allows_zero_rtt(&self) -> bool {
47 matches!(self.effect, RpcEffect::ReadOnly)
48 && matches!(self.retry_behavior, RetryBehavior::Safe)
49 }
50 pub fn client_operation_id<'a>(
53 &self,
54 request: &'a [u8],
55 ) -> Result<Option<&'a str>, crate::RequestMetadataError> {
56 self.client_operation_id_field_number
57 .map(|field| crate::transport::protobuf_string_field(request, field))
58 .transpose()
59 .map(Option::flatten)
60 }
61}
62
63pub const MAX_CURSOR_BYTES: usize = 4096;
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
68pub enum StreamProtocolError {
69 #[error("non-contiguous stream sequence")]
70 Sequence,
71 #[error("stream query binding mismatch")]
72 Binding,
73 #[error("stream resumed from an unexpected cursor")]
74 Resume,
75 #[error("invalid stream phase")]
76 Phase,
77 #[error("payload does not match the frame kind")]
78 Payload,
79 #[error("invalid or oversized checkpoint cursor")]
80 Cursor,
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84enum Phase {
85 Opening,
86 Snapshot,
87 Live,
88 Reset,
89 Complete,
90}
91
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
95pub enum ObservationAction {
96 BeginSnapshot,
97 Resumed,
98 Stage(StreamDataKind),
99 Commit,
100 Heartbeat,
101 Reset,
102 Complete,
103}
104
105#[derive(Debug, thiserror::Error)]
106pub enum ObservationApplyError<E> {
107 #[error("stream protocol failed: {0}")]
108 Protocol(#[from] StreamProtocolError),
109 #[error("view reducer failed: {0}")]
110 Reducer(E),
111}
112
113#[derive(Clone)]
115pub struct ObservationState {
116 binding_digest: [u8; 32],
117 cursor: Vec<u8>,
118 sequence: u64,
119 phase: Phase,
120 pending: bool,
121}
122
123impl ObservationState {
124 pub async fn apply<E, F, Fut>(
128 &mut self,
129 frame: &StreamFrame,
130 has_payload: bool,
131 reducer: F,
132 ) -> Result<ObservationAction, ObservationApplyError<E>>
133 where
134 F: FnOnce(ObservationAction, Vec<u8>) -> Fut,
135 Fut: std::future::Future<Output = Result<(), E>>,
136 {
137 let mut next = self.clone();
138 let action = next.accept(frame, has_payload)?;
139 reducer(action, next.cursor.clone())
140 .await
141 .map_err(ObservationApplyError::Reducer)?;
142 *self = next;
143 Ok(action)
144 }
145
146 pub fn new(binding_digest: [u8; 32], cursor: Vec<u8>) -> Self {
147 Self {
148 binding_digest,
149 cursor,
150 sequence: 0,
151 phase: Phase::Opening,
152 pending: false,
153 }
154 }
155
156 pub fn cursor(&self) -> &[u8] {
157 &self.cursor
158 }
159
160 pub fn is_complete(&self) -> bool {
161 self.phase == Phase::Complete
162 }
163
164 pub fn accept(
171 &mut self,
172 frame: &StreamFrame,
173 has_payload: bool,
174 ) -> Result<ObservationAction, StreamProtocolError> {
175 use stream_frame::Body;
176 if matches!(self.phase, Phase::Reset | Phase::Complete) {
177 return Err(StreamProtocolError::Phase);
178 }
179 if self.sequence.checked_add(1) != Some(frame.sequence) {
180 return Err(StreamProtocolError::Sequence);
181 }
182 let body = frame.body.as_ref().ok_or(StreamProtocolError::Phase)?;
183 if has_payload != matches!(body, Body::Data(_)) {
184 return Err(StreamProtocolError::Payload);
185 }
186 let action = match body {
187 Body::Open(open) => {
188 if self.phase != Phase::Opening {
189 return Err(StreamProtocolError::Phase);
190 }
191 if open.binding_digest.as_slice() != self.binding_digest {
192 return Err(StreamProtocolError::Binding);
193 }
194 if open.resumed_from != self.cursor {
195 return Err(StreamProtocolError::Resume);
196 }
197 if self.cursor.len() > MAX_CURSOR_BYTES {
198 return Err(StreamProtocolError::Cursor);
199 }
200 if self.cursor.is_empty() {
201 self.phase = Phase::Snapshot;
202 ObservationAction::BeginSnapshot
203 } else {
204 self.phase = Phase::Live;
205 ObservationAction::Resumed
206 }
207 }
208 Body::Data(data) => {
209 let kind =
210 StreamDataKind::try_from(data.kind).map_err(|_| StreamProtocolError::Phase)?;
211 let valid = match self.phase {
212 Phase::Snapshot => kind == StreamDataKind::Snapshot,
213 Phase::Live => matches!(kind, StreamDataKind::Upsert | StreamDataKind::Remove),
214 _ => false,
215 };
216 if !valid {
217 return Err(StreamProtocolError::Phase);
218 }
219 self.pending = true;
220 ObservationAction::Stage(kind)
221 }
222 Body::Checkpoint(checkpoint) => {
223 if !matches!(self.phase, Phase::Snapshot | Phase::Live)
224 || checkpoint.snapshot_complete != (self.phase == Phase::Snapshot)
225 {
226 return Err(StreamProtocolError::Phase);
227 }
228 if checkpoint.previous_cursor != self.cursor
229 || checkpoint.cursor.is_empty()
230 || checkpoint.cursor.len() > MAX_CURSOR_BYTES
231 || checkpoint.cursor == self.cursor
232 {
233 return Err(StreamProtocolError::Cursor);
234 }
235 self.cursor.clone_from(&checkpoint.cursor);
236 self.phase = Phase::Live;
237 self.pending = false;
238 ObservationAction::Commit
239 }
240 Body::Reset(_) => {
241 self.phase = Phase::Reset;
242 self.cursor.clear();
243 self.pending = false;
244 ObservationAction::Reset
245 }
246 Body::Complete(complete) => {
247 if self.phase != Phase::Live || self.pending {
248 return Err(StreamProtocolError::Phase);
249 }
250 if complete.cursor != self.cursor {
251 return Err(StreamProtocolError::Cursor);
252 }
253 self.phase = Phase::Complete;
254 ObservationAction::Complete
255 }
256 Body::Heartbeat(_) => {
257 if self.phase == Phase::Opening {
258 return Err(StreamProtocolError::Phase);
259 }
260 ObservationAction::Heartbeat
261 }
262 };
263 self.sequence = frame.sequence;
264 Ok(action)
265 }
266}