1use std::collections::BTreeMap;
4use std::marker::PhantomData;
5
6use crate::application::{
7 lower_value, raise_application_command_result, raise_value, typed_command_result,
8};
9use crate::generated::{GeneratedCommand, GeneratedEventConsumer, GeneratedLiveQuery};
10use crate::{
11 ApplicationCardinality, ApplicationClientError, ApplicationCommand, ApplicationCommandResult,
12 ApplicationRecord, ApplicationResultField, ApplicationValue, CallMetadata,
13 EventConsumerResponseStream, GeneratedExecutionError, LiveQueryUpdateStream, NamedQueryResult,
14 QueryResponseIdentity, StableApplicationClient, TypedCommandResult, generate_request_id, v1,
15};
16
17#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct ApplicationReactiveOperation {
20 module_hash: [u8; 32],
21 operation_name: String,
22 parameters: BTreeMap<String, ApplicationValue>,
23}
24
25impl ApplicationReactiveOperation {
26 pub fn new(
28 module_hash: [u8; 32],
29 operation_name: impl Into<String>,
30 parameters: BTreeMap<String, ApplicationValue>,
31 ) -> Result<Self, ApplicationClientError> {
32 let operation_name = operation_name.into();
33 if operation_name.is_empty()
34 || operation_name.len() > 256
35 || parameters.len() > 256
36 || parameters
37 .keys()
38 .any(|name| name.is_empty() || name.len() > 256)
39 {
40 return Err(ApplicationClientError::InvalidInput);
41 }
42 Ok(Self {
43 module_hash,
44 operation_name,
45 parameters,
46 })
47 }
48
49 fn lowered_parameters(
50 &self,
51 ) -> Result<Vec<v1::EventConsumerParameter>, ApplicationClientError> {
52 self.parameters
53 .iter()
54 .map(|(name, value)| {
55 Ok(v1::EventConsumerParameter {
56 name: name.clone(),
57 value: Some(lower_value(value.clone())?),
58 })
59 })
60 .collect()
61 }
62
63 fn live_parameters(&self) -> Result<Vec<v1::LiveQueryParameter>, ApplicationClientError> {
64 self.parameters
65 .iter()
66 .map(|(name, value)| {
67 Ok(v1::LiveQueryParameter {
68 name: name.clone(),
69 value: Some(lower_value(value.clone())?),
70 })
71 })
72 .collect()
73 }
74}
75
76#[derive(Clone, Debug, Eq, PartialEq)]
78pub struct ApplicationEventConsumer {
79 operation: ApplicationReactiveOperation,
80 consumer_name: String,
81}
82
83impl ApplicationEventConsumer {
84 pub fn new(
86 operation: ApplicationReactiveOperation,
87 consumer_name: impl Into<String>,
88 ) -> Result<Self, ApplicationClientError> {
89 let consumer_name = consumer_name.into();
90 let valid = consumer_name.len() <= 64
91 && consumer_name
92 .as_bytes()
93 .first()
94 .is_some_and(u8::is_ascii_alphabetic)
95 && consumer_name
96 .bytes()
97 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'));
98 if !valid {
99 return Err(ApplicationClientError::InvalidInput);
100 }
101 Ok(Self {
102 operation,
103 consumer_name,
104 })
105 }
106
107 fn selection(&self) -> Result<v1::EventConsumerSelection, ApplicationClientError> {
108 Ok(v1::EventConsumerSelection {
109 reactive_module_hash: self.operation.module_hash.to_vec(),
110 operation_name: self.operation.operation_name.clone(),
111 parameters: self.operation.lowered_parameters()?,
112 consumer_name: self.consumer_name.clone(),
113 })
114 }
115}
116
117#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub struct EventConsumerOptions {
120 pub batch_limit: u32,
122 pub in_flight_limit: u32,
124 pub lease_seconds: u64,
126 pub maximum_wait_nanos: u64,
128}
129
130impl Default for EventConsumerOptions {
131 fn default() -> Self {
132 Self {
133 batch_limit: 1,
134 in_flight_limit: 16,
135 lease_seconds: 60,
136 maximum_wait_nanos: 0,
137 }
138 }
139}
140
141impl EventConsumerOptions {
142 fn validate(self) -> Result<Self, ApplicationClientError> {
143 if !(1..=64).contains(&self.batch_limit)
144 || !(1..=64).contains(&self.in_flight_limit)
145 || !(5..=900).contains(&self.lease_seconds)
146 || self.maximum_wait_nanos > 30_000_000_000
147 {
148 return Err(ApplicationClientError::InvalidInput);
149 }
150 Ok(self)
151 }
152}
153
154#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
156pub struct ApplicationEventId {
157 pub commit_sequence: u64,
159 pub event_ordinal: u32,
161}
162
163#[derive(Clone, Debug, Eq, PartialEq)]
165pub struct LiveQueryCursor(Vec<u8>);
166
167impl LiveQueryCursor {
168 pub fn new(bytes: Vec<u8>) -> Result<Self, ApplicationClientError> {
170 if bytes.is_empty() || bytes.len() > 16_384 {
171 return Err(ApplicationClientError::InvalidInput);
172 }
173 Ok(Self(bytes))
174 }
175
176 #[must_use]
178 pub fn as_bytes(&self) -> &[u8] {
179 &self.0
180 }
181
182 pub fn from_standard_base64(value: &str) -> Result<Self, ApplicationClientError> {
185 decode_standard_base64(value).and_then(Self::new)
186 }
187
188 #[must_use]
190 pub fn to_standard_base64(&self) -> String {
191 encode_standard_base64(&self.0)
192 }
193}
194
195const BASE64_ALPHABET: &[u8; 64] =
196 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
197
198fn encode_standard_base64(value: &[u8]) -> String {
199 let mut output = String::with_capacity(value.len().div_ceil(3) * 4);
200 for chunk in value.chunks(3) {
201 let first = chunk[0];
202 let second = chunk.get(1).copied().unwrap_or(0);
203 let third = chunk.get(2).copied().unwrap_or(0);
204 output.push(char::from(BASE64_ALPHABET[usize::from(first >> 2)]));
205 output.push(char::from(
206 BASE64_ALPHABET[usize::from(((first & 0x03) << 4) | (second >> 4))],
207 ));
208 if chunk.len() >= 2 {
209 output.push(char::from(
210 BASE64_ALPHABET[usize::from(((second & 0x0f) << 2) | (third >> 6))],
211 ));
212 } else {
213 output.push('=');
214 }
215 if chunk.len() == 3 {
216 output.push(char::from(BASE64_ALPHABET[usize::from(third & 0x3f)]));
217 } else {
218 output.push('=');
219 }
220 }
221 output
222}
223
224fn decode_standard_base64(value: &str) -> Result<Vec<u8>, ApplicationClientError> {
225 let bytes = value.as_bytes();
226 if bytes.is_empty() || !bytes.len().is_multiple_of(4) || bytes.len() > 21_848 {
227 return Err(ApplicationClientError::InvalidInput);
228 }
229 let mut output = Vec::with_capacity(bytes.len() / 4 * 3);
230 let chunks = bytes.chunks_exact(4);
231 let chunk_count = chunks.len();
232 for (index, chunk) in chunks.enumerate() {
233 let last = index + 1 == chunk_count;
234 let first = base64_value(chunk[0]).ok_or(ApplicationClientError::InvalidInput)?;
235 let second = base64_value(chunk[1]).ok_or(ApplicationClientError::InvalidInput)?;
236 output.push((first << 2) | (second >> 4));
237 if chunk[2] == b'=' {
238 if !last || chunk[3] != b'=' || second & 0x0f != 0 {
239 return Err(ApplicationClientError::InvalidInput);
240 }
241 continue;
242 }
243 let third = base64_value(chunk[2]).ok_or(ApplicationClientError::InvalidInput)?;
244 output.push((second << 4) | (third >> 2));
245 if chunk[3] == b'=' {
246 if !last || third & 0x03 != 0 {
247 return Err(ApplicationClientError::InvalidInput);
248 }
249 continue;
250 }
251 let fourth = base64_value(chunk[3]).ok_or(ApplicationClientError::InvalidInput)?;
252 output.push((third << 6) | fourth);
253 }
254 Ok(output)
255}
256
257const fn base64_value(value: u8) -> Option<u8> {
258 match value {
259 b'A'..=b'Z' => Some(value - b'A'),
260 b'a'..=b'z' => Some(value - b'a' + 26),
261 b'0'..=b'9' => Some(value - b'0' + 52),
262 b'+' => Some(62),
263 b'/' => Some(63),
264 _ => None,
265 }
266}
267
268#[derive(Clone, Debug, Eq, PartialEq)]
270pub struct ApplicationEvent {
271 pub id: ApplicationEventId,
273 pub name: String,
275 pub writer_contract_version: u64,
277 pub command_name: String,
279 pub actor_kind: String,
281 pub provenance_uri: String,
283 pub history_incarnation: u64,
285 pub fields: BTreeMap<String, ApplicationValue>,
287}
288
289#[derive(Clone, Debug, Eq, PartialEq)]
291pub struct ApplicationEventDelivery {
292 pub event: ApplicationEvent,
294 pub attempt: u32,
296 pub lease_token: Vec<u8>,
298 pub expires_at: (i64, u32),
300}
301
302#[derive(Clone, Debug, Eq, PartialEq)]
304pub struct ApplicationEventLeaseEvidence {
305 event_id: ApplicationEventId,
306 lease_token: Vec<u8>,
307 history_incarnation: u64,
308}
309
310impl ApplicationEventLeaseEvidence {
311 pub fn new(
313 event_id: ApplicationEventId,
314 lease_token: Vec<u8>,
315 history_incarnation: u64,
316 ) -> Result<Self, ApplicationClientError> {
317 if event_id.commit_sequence == 0 || lease_token.len() != 32 || history_incarnation == 0 {
318 return Err(ApplicationClientError::InvalidInput);
319 }
320 Ok(Self {
321 event_id,
322 lease_token,
323 history_incarnation,
324 })
325 }
326}
327
328impl ApplicationEventDelivery {
329 #[must_use]
331 pub fn lease_evidence(&self) -> ApplicationEventLeaseEvidence {
332 ApplicationEventLeaseEvidence {
333 event_id: self.event.id,
334 lease_token: self.lease_token.clone(),
335 history_incarnation: self.event.history_incarnation,
336 }
337 }
338}
339
340#[derive(Clone, Copy, Debug, Eq, PartialEq)]
342pub enum ApplicationEventCheckpoint {
343 BeforeFirst,
345 After(ApplicationEventId),
347}
348
349#[derive(Clone, Debug, Eq, PartialEq)]
351pub struct ApplicationEventConsumerStatus {
352 pub revision: u64,
354 pub checkpoint: ApplicationEventCheckpoint,
356 pub history_incarnation: u64,
358 pub live_leases: u32,
360 pub retries: u32,
362 pub dead_letters: u32,
364}
365
366#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
369pub struct ApplicationEventProgressCursor([u8; 16]);
370
371impl ApplicationEventProgressCursor {
372 pub fn new(bytes: Vec<u8>) -> Result<Self, ApplicationClientError> {
374 bytes
375 .try_into()
376 .map(Self)
377 .map_err(|_| ApplicationClientError::InvalidInput)
378 }
379
380 #[must_use]
382 pub const fn as_bytes(&self) -> &[u8; 16] {
383 &self.0
384 }
385}
386
387#[derive(Clone, Copy, Debug, Eq, PartialEq)]
389pub struct ApplicationProtectedEventConsumerStatus {
390 pub history_incarnation: u64,
392 pub progress_cursor: ApplicationEventProgressCursor,
394}
395
396#[derive(Clone, Debug, Eq, PartialEq)]
398pub enum ApplicationEventConsumerPublicStatus {
399 Exact(ApplicationEventConsumerStatus),
401 Protected(ApplicationProtectedEventConsumerStatus),
403}
404
405#[derive(Clone, Copy, Debug, Eq, PartialEq)]
407pub enum ApplicationEventPullDisposition {
408 Ready,
410 WaitTimedOut,
412 BoundedProgress,
414}
415
416#[derive(Clone, Debug, Eq, PartialEq)]
418pub struct ApplicationEventBatch {
419 pub events: Vec<ApplicationEventDelivery>,
421 pub status: ApplicationEventConsumerPublicStatus,
423 pub disposition: ApplicationEventPullDisposition,
425 pub wait_timed_out: bool,
427}
428
429#[derive(Clone, Debug, Eq, PartialEq)]
431pub struct ApplicationContextualHydration {
432 pub name: String,
434 pub outcome: String,
436 pub fields: BTreeMap<String, ApplicationResultField>,
438}
439
440#[derive(Clone, Debug, Eq, PartialEq)]
442pub struct ApplicationContextualReaction {
443 pub name: String,
445 pub command_name: String,
447 pub command_id: u32,
449 causation_token: Vec<u8>,
450}
451
452impl ApplicationContextualReaction {
453 pub fn checked(
455 name: impl Into<String>,
456 command_name: impl Into<String>,
457 command_id: u32,
458 causation_token: Vec<u8>,
459 ) -> Result<Self, ApplicationClientError> {
460 let name = name.into();
461 let command_name = command_name.into();
462 if name.is_empty()
463 || name.len() > 256
464 || command_name.is_empty()
465 || command_name.len() > 256
466 || command_id == 0
467 || causation_token.len() <= 32
468 || causation_token.len() > 1_024
469 {
470 return Err(ApplicationClientError::InvalidInput);
471 }
472 Ok(Self {
473 name,
474 command_name,
475 command_id,
476 causation_token,
477 })
478 }
479
480 #[must_use]
482 pub fn causation_token(&self) -> &[u8] {
483 &self.causation_token
484 }
485}
486
487#[derive(Clone, Debug, Eq, PartialEq)]
489pub struct ApplicationContextualWorkItem {
490 pub delivery: ApplicationEventDelivery,
492 pub context_head: u64,
494 pub hydrations: Vec<ApplicationContextualHydration>,
496 pub available_reactions: Vec<ApplicationContextualReaction>,
498}
499
500#[derive(Clone, Debug, Eq, PartialEq)]
502pub struct ApplicationContextualBatch {
503 pub items: Vec<ApplicationContextualWorkItem>,
505 pub status: ApplicationEventConsumerPublicStatus,
507 pub disposition: ApplicationEventPullDisposition,
509 pub wait_timed_out: bool,
511}
512
513#[derive(Clone, Debug, Eq, PartialEq)]
515pub struct TypedContextualWorkItem<E> {
516 pub event: E,
518 evidence: ApplicationContextualWorkItem,
519}
520
521impl<E> TypedContextualWorkItem<E> {
522 #[must_use]
524 pub const fn evidence(&self) -> &ApplicationContextualWorkItem {
525 &self.evidence
526 }
527}
528
529#[derive(Clone, Debug, Eq, PartialEq)]
531pub struct TypedContextualBatch<E> {
532 pub items: Vec<TypedContextualWorkItem<E>>,
534 pub status: ApplicationEventConsumerPublicStatus,
536 pub disposition: ApplicationEventPullDisposition,
538 pub wait_timed_out: bool,
540}
541
542#[derive(Clone, Debug, Eq, PartialEq)]
544pub struct TypedEventDelivery<E> {
545 pub event: E,
547 evidence: ApplicationEventDelivery,
548}
549
550impl<E> TypedEventDelivery<E> {
551 #[must_use]
553 pub const fn evidence(&self) -> &ApplicationEventDelivery {
554 &self.evidence
555 }
556}
557
558#[derive(Clone, Debug, Eq, PartialEq)]
560pub struct TypedEventBatch<E> {
561 pub events: Vec<TypedEventDelivery<E>>,
563 pub status: ApplicationEventConsumerPublicStatus,
565 pub disposition: ApplicationEventPullDisposition,
567 pub wait_timed_out: bool,
569}
570
571#[derive(Clone, Copy, Debug, Eq, PartialEq)]
573pub enum ApplicationEventMutationResult {
574 Applied,
576 StateChanged,
578 NotFound,
580 OutstandingLease,
582 StaleLease,
584 LeaseExpired,
586}
587
588pub struct ApplicationEventResponseStream {
590 inner: EventConsumerResponseStream,
591}
592
593impl ApplicationEventResponseStream {
594 pub async fn message(
596 &mut self,
597 ) -> Result<Option<ApplicationEventBatch>, ApplicationClientError> {
598 self.inner
599 .message()
600 .await?
601 .map(raise_event_batch)
602 .transpose()
603 }
604}
605
606#[derive(Clone, Debug, Eq, PartialEq)]
608pub enum ApplicationLiveQueryUpdate {
609 Snapshot {
611 result: NamedQueryResult,
613 cursor: LiveQueryCursor,
615 history_incarnation: u64,
617 application_head: u64,
619 },
620 Patch(LiveQueryPatch),
622 Reset {
624 reason: String,
626 result: NamedQueryResult,
628 cursor: LiveQueryCursor,
630 history_incarnation: u64,
632 application_head: u64,
634 },
635 Checkpoint(LiveQueryCheckpoint),
637 Terminal(LiveQueryTerminal),
639}
640
641#[derive(Clone, Debug, Eq, PartialEq)]
643pub struct TypedLiveQuerySnapshot<T> {
644 pub result: T,
646 pub checkpoint: LiveQueryCheckpoint,
648}
649
650#[derive(Clone, Debug, Eq, PartialEq)]
652pub struct TypedLiveQueryReset<T> {
653 pub reason: String,
655 pub result: T,
657 pub checkpoint: LiveQueryCheckpoint,
659}
660
661#[derive(Clone, Debug, Eq, PartialEq)]
663pub struct LiveQueryPatch {
664 pub result_field: String,
666 pub operations: Vec<LiveQueryPatchOperation>,
668 pub checkpoint: LiveQueryCheckpoint,
670}
671
672#[derive(Clone, Debug, Eq, PartialEq)]
674pub enum LiveQueryPatchOperation {
675 Insert {
677 index: u32,
679 record: ApplicationRecord,
681 },
682 Remove {
684 index: u32,
686 key: BTreeMap<String, ApplicationValue>,
688 },
689 Replace {
691 index: u32,
693 record: ApplicationRecord,
695 },
696 Move {
698 from: u32,
700 to: u32,
702 key: BTreeMap<String, ApplicationValue>,
704 },
705}
706
707#[derive(Clone, Debug, Eq, PartialEq)]
709pub struct LiveQueryCheckpoint {
710 pub history_incarnation: u64,
712 pub application_head: u64,
714 pub cursor: LiveQueryCursor,
716}
717
718#[derive(Clone, Debug, Eq, PartialEq)]
720pub struct LiveQueryTerminal {
721 pub reason: String,
723 pub last_frontier: Option<(u64, u64)>,
725}
726
727pub struct ApplicationLiveQueryStream {
729 inner: LiveQueryUpdateStream,
730 terminal: bool,
731}
732
733pub struct TypedLiveQueryStream<G: GeneratedLiveQuery> {
735 inner: ApplicationLiveQueryStream,
736 marker: PhantomData<fn() -> G>,
737}
738
739impl<G: GeneratedLiveQuery> TypedLiveQueryStream<G> {
740 pub async fn message(&mut self) -> Result<Option<G::Update>, ApplicationClientError> {
742 self.inner
743 .message()
744 .await?
745 .map(G::decode_update)
746 .transpose()
747 }
748}
749
750impl ApplicationLiveQueryStream {
751 pub async fn message(
753 &mut self,
754 ) -> Result<Option<ApplicationLiveQueryUpdate>, ApplicationClientError> {
755 if self.terminal {
756 return Ok(None);
757 }
758 let update = self
759 .inner
760 .message()
761 .await?
762 .map(raise_live_update)
763 .transpose()?;
764 if matches!(update, Some(ApplicationLiveQueryUpdate::Terminal(_))) {
765 self.terminal = true;
766 }
767 Ok(update)
768 }
769}
770
771impl StableApplicationClient {
772 pub async fn consume_generated_events<G: GeneratedEventConsumer>(
774 &mut self,
775 generated: G,
776 options: EventConsumerOptions,
777 metadata: &CallMetadata,
778 ) -> Result<TypedEventBatch<G::Event>, ApplicationClientError> {
779 let consumer = generated.event_consumer()?;
780 let batch = self
781 .consume_event_stream(&consumer, options, metadata)
782 .await?;
783 let events = batch
784 .events
785 .into_iter()
786 .map(|evidence| {
787 Ok(TypedEventDelivery {
788 event: G::decode_event(evidence.event.clone())?,
789 evidence,
790 })
791 })
792 .collect::<Result<Vec<_>, ApplicationClientError>>()?;
793 Ok(TypedEventBatch {
794 events,
795 status: batch.status,
796 disposition: batch.disposition,
797 wait_timed_out: batch.wait_timed_out,
798 })
799 }
800
801 pub async fn consume_generated_contextual<G: GeneratedEventConsumer>(
803 &mut self,
804 generated: G,
805 maximum_wait_nanos: u64,
806 metadata: &CallMetadata,
807 ) -> Result<TypedContextualBatch<G::Event>, ApplicationClientError> {
808 let consumer = generated.event_consumer()?;
809 let batch = self
810 .consume_contextual_subscription(&consumer, maximum_wait_nanos, metadata)
811 .await?;
812 let items = batch
813 .items
814 .into_iter()
815 .map(|evidence| {
816 Ok(TypedContextualWorkItem {
817 event: G::decode_event(evidence.delivery.event.clone())?,
818 evidence,
819 })
820 })
821 .collect::<Result<Vec<_>, ApplicationClientError>>()?;
822 Ok(TypedContextualBatch {
823 items,
824 status: batch.status,
825 disposition: batch.disposition,
826 wait_timed_out: batch.wait_timed_out,
827 })
828 }
829
830 pub async fn execute_generated_contextual_reaction<C: GeneratedCommand>(
832 &mut self,
833 consumer: &ApplicationEventConsumer,
834 reaction: &ApplicationContextualReaction,
835 command: &C,
836 metadata: &CallMetadata,
837 ) -> Result<TypedCommandResult<C::Outcome>, GeneratedExecutionError> {
838 let request_id = generate_request_id().map_err(|error| {
839 GeneratedExecutionError::Client(crate::ClientError::IdentifierGeneration(error))
840 })?;
841 let command_request = command
842 .idempotent_command()
843 .map_err(GeneratedExecutionError::CommandShape)?;
844 if command_request.command_name() != reaction.command_name {
845 return Err(GeneratedExecutionError::CommandShape(
846 crate::generated::GeneratedCommandError::InvalidInputShape,
847 ));
848 }
849 let selection = consumer.selection().map_err(|_| {
850 GeneratedExecutionError::CommandShape(
851 crate::generated::GeneratedCommandError::InvalidInputShape,
852 )
853 })?;
854 let response = self
855 .inner
856 .execute_contextual_reaction(
857 v1::ExecuteContextualReactionRequest {
858 request_id: request_id.as_bytes().to_vec(),
859 selection: Some(selection),
860 causation_token: reaction.causation_token.clone(),
861 reaction_name: reaction.name.clone(),
862 command: Some(command_request.request(request_id)),
863 },
864 metadata,
865 )
866 .await
867 .map_err(GeneratedExecutionError::Client)?;
868 let outcome = command
869 .decode_outcome(&response)
870 .map_err(GeneratedExecutionError::CommandShape)?;
871 typed_command_result(command, outcome, response)
872 }
873
874 pub async fn watch_generated_query<G: GeneratedLiveQuery>(
876 &mut self,
877 generated: G,
878 cursor: Option<LiveQueryCursor>,
879 metadata: &CallMetadata,
880 ) -> Result<TypedLiveQueryStream<G>, ApplicationClientError> {
881 let operation = generated.live_operation()?;
882 let inner = self.watch_named_query(&operation, cursor, metadata).await?;
883 Ok(TypedLiveQueryStream {
884 inner,
885 marker: PhantomData,
886 })
887 }
888
889 pub async fn consume_event_stream(
891 &mut self,
892 consumer: &ApplicationEventConsumer,
893 options: EventConsumerOptions,
894 metadata: &CallMetadata,
895 ) -> Result<ApplicationEventBatch, ApplicationClientError> {
896 self.consume_event_stream_after(consumer, options, None, metadata)
897 .await
898 }
899
900 pub async fn consume_event_stream_after(
902 &mut self,
903 consumer: &ApplicationEventConsumer,
904 options: EventConsumerOptions,
905 progress_cursor: Option<ApplicationEventProgressCursor>,
906 metadata: &CallMetadata,
907 ) -> Result<ApplicationEventBatch, ApplicationClientError> {
908 let response = self
909 .inner
910 .consume_event_stream(
911 consumer_request(consumer, options, progress_cursor)?,
912 metadata,
913 )
914 .await?;
915 raise_event_batch(response)
916 }
917
918 pub async fn consume_contextual_subscription(
920 &mut self,
921 consumer: &ApplicationEventConsumer,
922 maximum_wait_nanos: u64,
923 metadata: &CallMetadata,
924 ) -> Result<ApplicationContextualBatch, ApplicationClientError> {
925 self.consume_contextual_subscription_after(consumer, maximum_wait_nanos, None, metadata)
926 .await
927 }
928
929 pub async fn consume_contextual_subscription_after(
931 &mut self,
932 consumer: &ApplicationEventConsumer,
933 maximum_wait_nanos: u64,
934 progress_cursor: Option<ApplicationEventProgressCursor>,
935 metadata: &CallMetadata,
936 ) -> Result<ApplicationContextualBatch, ApplicationClientError> {
937 if maximum_wait_nanos > 30_000_000_000 {
938 return Err(ApplicationClientError::InvalidInput);
939 }
940 let response = self
941 .inner
942 .consume_contextual_subscription(
943 v1::ConsumeContextualSubscriptionRequest {
944 request_id: request_id()?,
945 selection: Some(consumer.selection()?),
946 maximum_wait_nanos,
947 progress_cursor: progress_cursor
948 .map(|cursor| cursor.as_bytes().to_vec())
949 .unwrap_or_default(),
950 },
951 metadata,
952 )
953 .await?;
954 raise_contextual_batch(response)
955 }
956
957 pub async fn acknowledge_contextual_item(
959 &mut self,
960 consumer: &ApplicationEventConsumer,
961 item: &ApplicationContextualWorkItem,
962 metadata: &CallMetadata,
963 ) -> Result<ApplicationEventMutationResult, ApplicationClientError> {
964 self.acknowledge_contextual_lease(consumer, &item.delivery.lease_evidence(), metadata)
965 .await
966 }
967
968 pub async fn acknowledge_contextual_lease(
970 &mut self,
971 consumer: &ApplicationEventConsumer,
972 evidence: &ApplicationEventLeaseEvidence,
973 metadata: &CallMetadata,
974 ) -> Result<ApplicationEventMutationResult, ApplicationClientError> {
975 let response = self
976 .inner
977 .acknowledge_contextual_subscription(
978 v1::AcknowledgeContextualSubscriptionRequest {
979 request_id: request_id()?,
980 selection: Some(consumer.selection()?),
981 event_id: Some(lower_event_id(evidence.event_id)),
982 lease_token: evidence.lease_token.clone(),
983 history_incarnation: evidence.history_incarnation,
984 },
985 metadata,
986 )
987 .await?;
988 raise_mutation_result(response.result)
989 }
990
991 pub async fn negative_acknowledge_contextual_item(
993 &mut self,
994 consumer: &ApplicationEventConsumer,
995 item: &ApplicationContextualWorkItem,
996 retry_delay_nanos: u64,
997 metadata: &CallMetadata,
998 ) -> Result<ApplicationEventMutationResult, ApplicationClientError> {
999 self.negative_acknowledge_contextual_lease(
1000 consumer,
1001 &item.delivery.lease_evidence(),
1002 retry_delay_nanos,
1003 metadata,
1004 )
1005 .await
1006 }
1007
1008 pub async fn negative_acknowledge_contextual_lease(
1010 &mut self,
1011 consumer: &ApplicationEventConsumer,
1012 evidence: &ApplicationEventLeaseEvidence,
1013 retry_delay_nanos: u64,
1014 metadata: &CallMetadata,
1015 ) -> Result<ApplicationEventMutationResult, ApplicationClientError> {
1016 if retry_delay_nanos > 3_600_000_000_000 {
1017 return Err(ApplicationClientError::InvalidInput);
1018 }
1019 let response = self
1020 .inner
1021 .negative_acknowledge_contextual_subscription(
1022 v1::NegativeAcknowledgeContextualSubscriptionRequest {
1023 request_id: request_id()?,
1024 selection: Some(consumer.selection()?),
1025 event_id: Some(lower_event_id(evidence.event_id)),
1026 lease_token: evidence.lease_token.clone(),
1027 history_incarnation: evidence.history_incarnation,
1028 retry_delay_nanos,
1029 },
1030 metadata,
1031 )
1032 .await?;
1033 raise_mutation_result(response.result)
1034 }
1035
1036 pub async fn contextual_subscription_status(
1038 &mut self,
1039 consumer: &ApplicationEventConsumer,
1040 metadata: &CallMetadata,
1041 ) -> Result<Option<ApplicationEventConsumerPublicStatus>, ApplicationClientError> {
1042 let response = self
1043 .inner
1044 .get_contextual_subscription_status(
1045 v1::GetContextualSubscriptionStatusRequest {
1046 request_id: request_id()?,
1047 selection: Some(consumer.selection()?),
1048 },
1049 metadata,
1050 )
1051 .await?;
1052 match response.result {
1053 Some(v1::get_event_stream_consumer_status_response::Result::NotFound(_)) => Ok(None),
1054 Some(v1::get_event_stream_consumer_status_response::Result::Found(status)) => {
1055 raise_consumer_status(status)
1056 .map(ApplicationEventConsumerPublicStatus::Exact)
1057 .map(Some)
1058 }
1059 Some(v1::get_event_stream_consumer_status_response::Result::Protected(status)) => {
1060 raise_protected_consumer_status(status).map(Some)
1061 }
1062 None => Err(ApplicationClientError::InvalidResponse),
1063 }
1064 }
1065
1066 pub async fn execute_contextual_reaction(
1068 &mut self,
1069 consumer: &ApplicationEventConsumer,
1070 reaction: &ApplicationContextualReaction,
1071 command: ApplicationCommand,
1072 metadata: &CallMetadata,
1073 ) -> Result<ApplicationCommandResult, ApplicationClientError> {
1074 let request_id =
1075 generate_request_id().map_err(|_| ApplicationClientError::IdentifierUnavailable)?;
1076 let command = command.into_idempotent_command()?;
1077 if command.command_name() != reaction.command_name {
1078 return Err(ApplicationClientError::InvalidInput);
1079 }
1080 let response = self
1081 .inner
1082 .execute_contextual_reaction(
1083 v1::ExecuteContextualReactionRequest {
1084 request_id: request_id.into_bytes().to_vec(),
1085 selection: Some(consumer.selection()?),
1086 causation_token: reaction.causation_token.clone(),
1087 reaction_name: reaction.name.clone(),
1088 command: Some(command.request(request_id)),
1089 },
1090 metadata,
1091 )
1092 .await?;
1093 raise_application_command_result(response)
1094 }
1095
1096 pub async fn stream_event_consumer(
1098 &mut self,
1099 consumer: &ApplicationEventConsumer,
1100 options: EventConsumerOptions,
1101 metadata: &CallMetadata,
1102 ) -> Result<ApplicationEventResponseStream, ApplicationClientError> {
1103 let inner = self
1104 .inner
1105 .stream_event_consumer(consumer_request(consumer, options, None)?, metadata)
1106 .await?;
1107 Ok(ApplicationEventResponseStream { inner })
1108 }
1109
1110 pub async fn acknowledge_event(
1112 &mut self,
1113 consumer: &ApplicationEventConsumer,
1114 delivery: &ApplicationEventDelivery,
1115 metadata: &CallMetadata,
1116 ) -> Result<ApplicationEventMutationResult, ApplicationClientError> {
1117 self.acknowledge_event_lease(consumer, &delivery.lease_evidence(), metadata)
1118 .await
1119 }
1120
1121 pub async fn acknowledge_event_lease(
1123 &mut self,
1124 consumer: &ApplicationEventConsumer,
1125 evidence: &ApplicationEventLeaseEvidence,
1126 metadata: &CallMetadata,
1127 ) -> Result<ApplicationEventMutationResult, ApplicationClientError> {
1128 let response = self
1129 .inner
1130 .acknowledge_event_stream(
1131 v1::AcknowledgeEventStreamRequest {
1132 request_id: request_id()?,
1133 selection: Some(consumer.selection()?),
1134 event_id: Some(lower_event_id(evidence.event_id)),
1135 lease_token: evidence.lease_token.clone(),
1136 history_incarnation: evidence.history_incarnation,
1137 },
1138 metadata,
1139 )
1140 .await?;
1141 raise_mutation_result(response.result)
1142 }
1143
1144 pub async fn negative_acknowledge_event(
1146 &mut self,
1147 consumer: &ApplicationEventConsumer,
1148 delivery: &ApplicationEventDelivery,
1149 retry_delay_nanos: u64,
1150 metadata: &CallMetadata,
1151 ) -> Result<ApplicationEventMutationResult, ApplicationClientError> {
1152 self.negative_acknowledge_event_lease(
1153 consumer,
1154 &delivery.lease_evidence(),
1155 retry_delay_nanos,
1156 metadata,
1157 )
1158 .await
1159 }
1160
1161 pub async fn negative_acknowledge_event_lease(
1163 &mut self,
1164 consumer: &ApplicationEventConsumer,
1165 evidence: &ApplicationEventLeaseEvidence,
1166 retry_delay_nanos: u64,
1167 metadata: &CallMetadata,
1168 ) -> Result<ApplicationEventMutationResult, ApplicationClientError> {
1169 if retry_delay_nanos > 3_600_000_000_000 {
1170 return Err(ApplicationClientError::InvalidInput);
1171 }
1172 let response = self
1173 .inner
1174 .negative_acknowledge_event_stream(
1175 v1::NegativeAcknowledgeEventStreamRequest {
1176 request_id: request_id()?,
1177 selection: Some(consumer.selection()?),
1178 event_id: Some(lower_event_id(evidence.event_id)),
1179 lease_token: evidence.lease_token.clone(),
1180 history_incarnation: evidence.history_incarnation,
1181 retry_delay_nanos,
1182 },
1183 metadata,
1184 )
1185 .await?;
1186 raise_mutation_result(response.result)
1187 }
1188
1189 pub async fn seek_event_consumer(
1191 &mut self,
1192 consumer: &ApplicationEventConsumer,
1193 checkpoint: ApplicationEventCheckpoint,
1194 metadata: &CallMetadata,
1195 ) -> Result<ApplicationEventMutationResult, ApplicationClientError> {
1196 let position = match checkpoint {
1197 ApplicationEventCheckpoint::BeforeFirst => {
1198 v1::event_consumer_checkpoint::Position::BeforeFirst(v1::Unit {})
1199 }
1200 ApplicationEventCheckpoint::After(event_id) if event_id.commit_sequence != 0 => {
1201 v1::event_consumer_checkpoint::Position::AfterEventId(lower_event_id(event_id))
1202 }
1203 ApplicationEventCheckpoint::After(_) => {
1204 return Err(ApplicationClientError::InvalidInput);
1205 }
1206 };
1207 let response = self
1208 .inner
1209 .seek_event_stream_consumer(
1210 v1::SeekEventStreamConsumerRequest {
1211 request_id: request_id()?,
1212 selection: Some(consumer.selection()?),
1213 checkpoint: Some(v1::EventConsumerCheckpoint {
1214 position: Some(position),
1215 }),
1216 progress_cursor: Vec::new(),
1217 },
1218 metadata,
1219 )
1220 .await?;
1221 raise_mutation_result(response.result)
1222 }
1223
1224 pub async fn seek_protected_event_consumer(
1227 &mut self,
1228 consumer: &ApplicationEventConsumer,
1229 cursor: ApplicationEventProgressCursor,
1230 metadata: &CallMetadata,
1231 ) -> Result<ApplicationEventMutationResult, ApplicationClientError> {
1232 let response = self
1233 .inner
1234 .seek_event_stream_consumer(
1235 v1::SeekEventStreamConsumerRequest {
1236 request_id: request_id()?,
1237 selection: Some(consumer.selection()?),
1238 checkpoint: None,
1239 progress_cursor: cursor.as_bytes().to_vec(),
1240 },
1241 metadata,
1242 )
1243 .await?;
1244 raise_mutation_result(response.result)
1245 }
1246
1247 pub async fn event_consumer_status(
1249 &mut self,
1250 consumer: &ApplicationEventConsumer,
1251 metadata: &CallMetadata,
1252 ) -> Result<Option<ApplicationEventConsumerPublicStatus>, ApplicationClientError> {
1253 let response = self
1254 .inner
1255 .get_event_stream_consumer_status(
1256 v1::GetEventStreamConsumerStatusRequest {
1257 request_id: request_id()?,
1258 selection: Some(consumer.selection()?),
1259 },
1260 metadata,
1261 )
1262 .await?;
1263 match response.result {
1264 Some(v1::get_event_stream_consumer_status_response::Result::NotFound(_)) => Ok(None),
1265 Some(v1::get_event_stream_consumer_status_response::Result::Found(status)) => {
1266 raise_consumer_status(status)
1267 .map(ApplicationEventConsumerPublicStatus::Exact)
1268 .map(Some)
1269 }
1270 Some(v1::get_event_stream_consumer_status_response::Result::Protected(status)) => {
1271 raise_protected_consumer_status(status).map(Some)
1272 }
1273 None => Err(ApplicationClientError::InvalidResponse),
1274 }
1275 }
1276
1277 pub async fn watch_named_query(
1279 &mut self,
1280 operation: &ApplicationReactiveOperation,
1281 cursor: Option<LiveQueryCursor>,
1282 metadata: &CallMetadata,
1283 ) -> Result<ApplicationLiveQueryStream, ApplicationClientError> {
1284 let inner = self
1285 .inner
1286 .watch_named_query(
1287 v1::WatchNamedQueryRequest {
1288 request_id: request_id()?,
1289 reactive_module_hash: operation.module_hash.to_vec(),
1290 operation_name: operation.operation_name.clone(),
1291 parameters: operation.live_parameters()?,
1292 cursor: cursor.map(|value| value.0),
1293 },
1294 metadata,
1295 )
1296 .await?;
1297 Ok(ApplicationLiveQueryStream {
1298 inner,
1299 terminal: false,
1300 })
1301 }
1302}
1303
1304fn consumer_request(
1305 consumer: &ApplicationEventConsumer,
1306 options: EventConsumerOptions,
1307 progress_cursor: Option<ApplicationEventProgressCursor>,
1308) -> Result<v1::ConsumeEventStreamRequest, ApplicationClientError> {
1309 let options = options.validate()?;
1310 Ok(v1::ConsumeEventStreamRequest {
1311 request_id: request_id()?,
1312 selection: Some(consumer.selection()?),
1313 batch_limit: options.batch_limit,
1314 in_flight_limit: options.in_flight_limit,
1315 lease_seconds: options.lease_seconds,
1316 maximum_wait_nanos: options.maximum_wait_nanos,
1317 progress_cursor: progress_cursor
1318 .map(|cursor| cursor.as_bytes().to_vec())
1319 .unwrap_or_default(),
1320 })
1321}
1322
1323fn request_id() -> Result<Vec<u8>, ApplicationClientError> {
1324 generate_request_id()
1325 .map(|value| value.into_bytes().to_vec())
1326 .map_err(|_| ApplicationClientError::IdentifierUnavailable)
1327}
1328
1329const fn lower_event_id(value: ApplicationEventId) -> v1::EventId {
1330 v1::EventId {
1331 commit_sequence: value.commit_sequence,
1332 event_ordinal: value.event_ordinal,
1333 }
1334}
1335
1336fn raise_event_id(
1337 value: Option<v1::EventId>,
1338) -> Result<ApplicationEventId, ApplicationClientError> {
1339 let value = value.ok_or(ApplicationClientError::InvalidResponse)?;
1340 if value.commit_sequence == 0 {
1341 return Err(ApplicationClientError::InvalidResponse);
1342 }
1343 Ok(ApplicationEventId {
1344 commit_sequence: value.commit_sequence,
1345 event_ordinal: value.event_ordinal,
1346 })
1347}
1348
1349fn raise_event_batch(
1350 response: v1::ConsumeEventStreamResponse,
1351) -> Result<ApplicationEventBatch, ApplicationClientError> {
1352 let events = response
1353 .events
1354 .into_iter()
1355 .map(raise_event_delivery)
1356 .collect::<Result<Vec<_>, _>>()?;
1357 let status = raise_consumer_public_status(response.status, response.protected_status)?;
1358 let disposition = raise_consumer_disposition(response.disposition, response.wait_timed_out)?;
1359 if disposition != ApplicationEventPullDisposition::Ready && !events.is_empty() {
1360 return Err(ApplicationClientError::InvalidResponse);
1361 }
1362 Ok(ApplicationEventBatch {
1363 events,
1364 status,
1365 disposition,
1366 wait_timed_out: response.wait_timed_out,
1367 })
1368}
1369
1370fn raise_contextual_batch(
1371 response: v1::ConsumeContextualSubscriptionResponse,
1372) -> Result<ApplicationContextualBatch, ApplicationClientError> {
1373 let items = response
1374 .items
1375 .into_iter()
1376 .map(raise_contextual_work_item)
1377 .collect::<Result<Vec<_>, _>>()?;
1378 let status = raise_consumer_public_status(response.status, response.protected_status)?;
1379 let disposition = raise_consumer_disposition(response.disposition, response.wait_timed_out)?;
1380 if disposition != ApplicationEventPullDisposition::Ready && !items.is_empty() {
1381 return Err(ApplicationClientError::InvalidResponse);
1382 }
1383 Ok(ApplicationContextualBatch {
1384 items,
1385 status,
1386 disposition,
1387 wait_timed_out: response.wait_timed_out,
1388 })
1389}
1390
1391fn raise_contextual_work_item(
1392 item: v1::ContextualWorkItem,
1393) -> Result<ApplicationContextualWorkItem, ApplicationClientError> {
1394 let delivery = raise_event_delivery(
1395 item.delivery
1396 .ok_or(ApplicationClientError::InvalidResponse)?,
1397 )?;
1398 let hydrations = item
1399 .hydrations
1400 .into_iter()
1401 .map(raise_contextual_hydration)
1402 .collect::<Result<Vec<_>, _>>()?;
1403 let available_reactions = item
1404 .available_reactions
1405 .into_iter()
1406 .map(|reaction| {
1407 if reaction.name.is_empty()
1408 || reaction.command_name.is_empty()
1409 || reaction.command_id == 0
1410 || reaction.causation_token.len() <= 32
1411 || reaction.causation_token.len() > 1_024
1412 {
1413 return Err(ApplicationClientError::InvalidResponse);
1414 }
1415 Ok(ApplicationContextualReaction {
1416 name: reaction.name,
1417 command_name: reaction.command_name,
1418 command_id: reaction.command_id,
1419 causation_token: reaction.causation_token,
1420 })
1421 })
1422 .collect::<Result<Vec<_>, _>>()?;
1423 if item.context_head == 0 {
1424 return Err(ApplicationClientError::InvalidResponse);
1425 }
1426 Ok(ApplicationContextualWorkItem {
1427 delivery,
1428 context_head: item.context_head,
1429 hydrations,
1430 available_reactions,
1431 })
1432}
1433
1434fn raise_contextual_hydration(
1435 hydration: v1::ContextualHydration,
1436) -> Result<ApplicationContextualHydration, ApplicationClientError> {
1437 let mut fields = BTreeMap::new();
1438 for field in hydration.fields {
1439 let cardinality = match v1::ContextualQueryCardinality::try_from(field.cardinality).ok() {
1440 Some(v1::ContextualQueryCardinality::One) => ApplicationCardinality::One,
1441 Some(v1::ContextualQueryCardinality::Maybe) => ApplicationCardinality::Maybe,
1442 Some(v1::ContextualQueryCardinality::Many) => ApplicationCardinality::Many,
1443 Some(v1::ContextualQueryCardinality::Unspecified) | None => {
1444 return Err(ApplicationClientError::InvalidResponse);
1445 }
1446 };
1447 let records = field
1448 .rows
1449 .into_iter()
1450 .map(|row| {
1451 let mut values = BTreeMap::new();
1452 for value in row.fields {
1453 if value.name.is_empty()
1454 || values
1455 .insert(
1456 value.name,
1457 raise_value(
1458 value.value.ok_or(ApplicationClientError::InvalidResponse)?,
1459 )?,
1460 )
1461 .is_some()
1462 {
1463 return Err(ApplicationClientError::InvalidResponse);
1464 }
1465 }
1466 Ok(ApplicationRecord {
1467 entity: row.entity,
1468 fields: values,
1469 })
1470 })
1471 .collect::<Result<Vec<_>, _>>()?;
1472 if field.name.is_empty()
1473 || fields
1474 .insert(
1475 field.name,
1476 ApplicationResultField {
1477 cardinality,
1478 records,
1479 },
1480 )
1481 .is_some()
1482 {
1483 return Err(ApplicationClientError::InvalidResponse);
1484 }
1485 }
1486 if hydration.name.is_empty() || hydration.outcome.is_empty() {
1487 return Err(ApplicationClientError::InvalidResponse);
1488 }
1489 Ok(ApplicationContextualHydration {
1490 name: hydration.name,
1491 outcome: hydration.outcome,
1492 fields,
1493 })
1494}
1495
1496fn raise_event_delivery(
1497 delivery: v1::ConsumedEvent,
1498) -> Result<ApplicationEventDelivery, ApplicationClientError> {
1499 let event = delivery
1500 .event
1501 .ok_or(ApplicationClientError::InvalidResponse)?;
1502 let id = raise_event_id(event.event_id)?;
1503 let expires = delivery
1504 .expires_at
1505 .ok_or(ApplicationClientError::InvalidResponse)?;
1506 let mut fields = BTreeMap::new();
1507 for field in event.fields {
1508 if field.name.is_empty()
1509 || fields
1510 .insert(
1511 field.name,
1512 raise_value(field.value.ok_or(ApplicationClientError::InvalidResponse)?)?,
1513 )
1514 .is_some()
1515 {
1516 return Err(ApplicationClientError::InvalidResponse);
1517 }
1518 }
1519 if event.event_name.is_empty()
1520 || event.command_name.is_empty()
1521 || event.provenance_uri.is_empty()
1522 || event.history_incarnation == 0
1523 || delivery.attempt == 0
1524 || delivery.lease_token.is_empty()
1525 || expires.nanos >= 1_000_000_000
1526 {
1527 return Err(ApplicationClientError::InvalidResponse);
1528 }
1529 let actor_kind = v1::ActorKind::try_from(event.actor_kind)
1530 .ok()
1531 .filter(|kind| *kind != v1::ActorKind::Unspecified)
1532 .map(|kind| kind.as_str_name().to_owned())
1533 .ok_or(ApplicationClientError::InvalidResponse)?;
1534 Ok(ApplicationEventDelivery {
1535 event: ApplicationEvent {
1536 id,
1537 name: event.event_name,
1538 writer_contract_version: event.writer_contract_version,
1539 command_name: event.command_name,
1540 actor_kind,
1541 provenance_uri: event.provenance_uri,
1542 history_incarnation: event.history_incarnation,
1543 fields,
1544 },
1545 attempt: delivery.attempt,
1546 lease_token: delivery.lease_token,
1547 expires_at: (expires.seconds, expires.nanos),
1548 })
1549}
1550
1551fn raise_consumer_status(
1552 status: v1::EventConsumerStatus,
1553) -> Result<ApplicationEventConsumerStatus, ApplicationClientError> {
1554 let checkpoint = match status
1555 .checkpoint
1556 .and_then(|value| value.position)
1557 .ok_or(ApplicationClientError::InvalidResponse)?
1558 {
1559 v1::event_consumer_checkpoint::Position::BeforeFirst(_) => {
1560 ApplicationEventCheckpoint::BeforeFirst
1561 }
1562 v1::event_consumer_checkpoint::Position::AfterEventId(value) => {
1563 ApplicationEventCheckpoint::After(raise_event_id(Some(value))?)
1564 }
1565 };
1566 if status.revision == 0 || status.history_incarnation == 0 {
1567 return Err(ApplicationClientError::InvalidResponse);
1568 }
1569 Ok(ApplicationEventConsumerStatus {
1570 revision: status.revision,
1571 checkpoint,
1572 history_incarnation: status.history_incarnation,
1573 live_leases: status.live_leases,
1574 retries: status.retries,
1575 dead_letters: status.dead_letters,
1576 })
1577}
1578
1579fn raise_consumer_public_status(
1580 exact: Option<v1::EventConsumerStatus>,
1581 protected: Option<v1::ProtectedEventConsumerStatus>,
1582) -> Result<ApplicationEventConsumerPublicStatus, ApplicationClientError> {
1583 match (exact, protected) {
1584 (Some(status), None) => {
1585 raise_consumer_status(status).map(ApplicationEventConsumerPublicStatus::Exact)
1586 }
1587 (None, Some(status)) => raise_protected_consumer_status(status),
1588 _ => Err(ApplicationClientError::InvalidResponse),
1589 }
1590}
1591
1592fn raise_protected_consumer_status(
1593 status: v1::ProtectedEventConsumerStatus,
1594) -> Result<ApplicationEventConsumerPublicStatus, ApplicationClientError> {
1595 if status.history_incarnation == 0 {
1596 return Err(ApplicationClientError::InvalidResponse);
1597 }
1598 Ok(ApplicationEventConsumerPublicStatus::Protected(
1599 ApplicationProtectedEventConsumerStatus {
1600 history_incarnation: status.history_incarnation,
1601 progress_cursor: ApplicationEventProgressCursor::new(status.progress_cursor)
1602 .map_err(|_| ApplicationClientError::InvalidResponse)?,
1603 },
1604 ))
1605}
1606
1607fn raise_consumer_disposition(
1608 disposition: i32,
1609 wait_timed_out: bool,
1610) -> Result<ApplicationEventPullDisposition, ApplicationClientError> {
1611 let disposition = match v1::EventConsumerPullDisposition::try_from(disposition).ok() {
1612 Some(v1::EventConsumerPullDisposition::Ready) => ApplicationEventPullDisposition::Ready,
1613 Some(v1::EventConsumerPullDisposition::WaitTimedOut) => {
1614 ApplicationEventPullDisposition::WaitTimedOut
1615 }
1616 Some(v1::EventConsumerPullDisposition::BoundedProgress) => {
1617 ApplicationEventPullDisposition::BoundedProgress
1618 }
1619 _ => return Err(ApplicationClientError::InvalidResponse),
1620 };
1621 if wait_timed_out != (disposition == ApplicationEventPullDisposition::WaitTimedOut) {
1622 return Err(ApplicationClientError::InvalidResponse);
1623 }
1624 Ok(disposition)
1625}
1626
1627fn raise_mutation_result(
1628 value: i32,
1629) -> Result<ApplicationEventMutationResult, ApplicationClientError> {
1630 match v1::EventConsumerMutationResult::try_from(value).ok() {
1631 Some(v1::EventConsumerMutationResult::Applied) => {
1632 Ok(ApplicationEventMutationResult::Applied)
1633 }
1634 Some(v1::EventConsumerMutationResult::StateChanged) => {
1635 Ok(ApplicationEventMutationResult::StateChanged)
1636 }
1637 Some(v1::EventConsumerMutationResult::NotFound) => {
1638 Ok(ApplicationEventMutationResult::NotFound)
1639 }
1640 Some(v1::EventConsumerMutationResult::OutstandingLease) => {
1641 Ok(ApplicationEventMutationResult::OutstandingLease)
1642 }
1643 Some(v1::EventConsumerMutationResult::StaleLease) => {
1644 Ok(ApplicationEventMutationResult::StaleLease)
1645 }
1646 Some(v1::EventConsumerMutationResult::LeaseExpired) => {
1647 Ok(ApplicationEventMutationResult::LeaseExpired)
1648 }
1649 Some(v1::EventConsumerMutationResult::Unspecified) | None => {
1650 Err(ApplicationClientError::InvalidResponse)
1651 }
1652 }
1653}
1654
1655fn raise_live_update(
1656 update: v1::LiveQueryUpdate,
1657) -> Result<ApplicationLiveQueryUpdate, ApplicationClientError> {
1658 match update
1659 .update
1660 .ok_or(ApplicationClientError::InvalidResponse)?
1661 {
1662 v1::live_query_update::Update::Snapshot(value) => {
1663 let checkpoint = live_checkpoint(value.frontier, value.cursor)?;
1664 Ok(ApplicationLiveQueryUpdate::Snapshot {
1665 result: raise_live_result(value.result)?,
1666 cursor: checkpoint.cursor,
1667 history_incarnation: checkpoint.history_incarnation,
1668 application_head: checkpoint.application_head,
1669 })
1670 }
1671 v1::live_query_update::Update::Patch(value) => {
1672 let checkpoint = live_checkpoint(value.frontier, value.cursor)?;
1673 let operations = value
1674 .operations
1675 .into_iter()
1676 .map(raise_patch_operation)
1677 .collect::<Result<Vec<_>, _>>()?;
1678 Ok(ApplicationLiveQueryUpdate::Patch(LiveQueryPatch {
1679 result_field: value.result_field,
1680 operations,
1681 checkpoint,
1682 }))
1683 }
1684 v1::live_query_update::Update::Reset(value) => {
1685 let checkpoint = live_checkpoint(value.frontier, value.cursor)?;
1686 let reason = v1::LiveQueryResetReason::try_from(value.reason)
1687 .ok()
1688 .filter(|reason| *reason != v1::LiveQueryResetReason::Unspecified)
1689 .map(|reason| reason.as_str_name().to_owned())
1690 .ok_or(ApplicationClientError::InvalidResponse)?;
1691 Ok(ApplicationLiveQueryUpdate::Reset {
1692 reason,
1693 result: raise_live_result(value.result)?,
1694 cursor: checkpoint.cursor,
1695 history_incarnation: checkpoint.history_incarnation,
1696 application_head: checkpoint.application_head,
1697 })
1698 }
1699 v1::live_query_update::Update::Checkpoint(value) => Ok(
1700 ApplicationLiveQueryUpdate::Checkpoint(live_checkpoint(value.frontier, value.cursor)?),
1701 ),
1702 v1::live_query_update::Update::Terminal(value) => {
1703 let reason = v1::LiveQueryTerminalReason::try_from(value.reason)
1704 .ok()
1705 .filter(|reason| *reason != v1::LiveQueryTerminalReason::Unspecified)
1706 .map(|reason| reason.as_str_name().to_owned())
1707 .ok_or(ApplicationClientError::InvalidResponse)?;
1708 let last_frontier = value
1709 .last_frontier
1710 .map(|frontier| (frontier.history_incarnation, frontier.application_head));
1711 Ok(ApplicationLiveQueryUpdate::Terminal(LiveQueryTerminal {
1712 reason,
1713 last_frontier,
1714 }))
1715 }
1716 }
1717}
1718
1719fn live_checkpoint(
1720 frontier: Option<v1::LiveQueryFrontier>,
1721 cursor: Vec<u8>,
1722) -> Result<LiveQueryCheckpoint, ApplicationClientError> {
1723 let frontier = frontier.ok_or(ApplicationClientError::InvalidResponse)?;
1724 if frontier.history_incarnation == 0 {
1725 return Err(ApplicationClientError::InvalidResponse);
1726 }
1727 Ok(LiveQueryCheckpoint {
1728 history_incarnation: frontier.history_incarnation,
1729 application_head: frontier.application_head,
1730 cursor: LiveQueryCursor::new(cursor)
1731 .map_err(|_| ApplicationClientError::InvalidResponse)?,
1732 })
1733}
1734
1735fn raise_live_result(
1736 result: Option<v1::LiveQueryResult>,
1737) -> Result<NamedQueryResult, ApplicationClientError> {
1738 let result = result.ok_or(ApplicationClientError::InvalidResponse)?;
1739 let identity = result
1740 .identity
1741 .ok_or(ApplicationClientError::InvalidResponse)?;
1742 if result.outcome.is_empty() {
1743 return Err(ApplicationClientError::InvalidResponse);
1744 }
1745 let mut fields = BTreeMap::new();
1746 for field in result.fields {
1747 let cardinality = match v1::LiveQueryResultCardinality::try_from(field.cardinality).ok() {
1748 Some(v1::LiveQueryResultCardinality::One) => ApplicationCardinality::One,
1749 Some(v1::LiveQueryResultCardinality::Maybe) => ApplicationCardinality::Maybe,
1750 Some(v1::LiveQueryResultCardinality::Many) => ApplicationCardinality::Many,
1751 Some(v1::LiveQueryResultCardinality::Unspecified) | None => {
1752 return Err(ApplicationClientError::InvalidResponse);
1753 }
1754 };
1755 let records = field
1756 .records
1757 .into_iter()
1758 .map(raise_live_record)
1759 .collect::<Result<Vec<_>, _>>()?;
1760 if field.name.is_empty()
1761 || fields
1762 .insert(
1763 field.name,
1764 ApplicationResultField {
1765 cardinality,
1766 records,
1767 },
1768 )
1769 .is_some()
1770 {
1771 return Err(ApplicationClientError::InvalidResponse);
1772 }
1773 }
1774 let contract_bundle_hash = identity
1775 .contract_bundle_hash
1776 .try_into()
1777 .map_err(|_| ApplicationClientError::InvalidResponse)?;
1778 let module_hash = identity
1779 .query_module_hash
1780 .try_into()
1781 .map_err(|_| ApplicationClientError::InvalidResponse)?;
1782 let plan_hash = identity
1783 .query_plan_hash
1784 .try_into()
1785 .map_err(|_| ApplicationClientError::InvalidResponse)?;
1786 Ok(NamedQueryResult {
1787 outcome: result.outcome,
1788 application_head: 0,
1789 fields,
1790 next_cursor: None,
1791 identity: QueryResponseIdentity {
1792 contract_lineage: identity.contract_lineage,
1793 contract_version: identity.contract_version,
1794 contract_bundle_hash,
1795 module_hash,
1796 query_name: identity.query_name,
1797 plan_hash,
1798 },
1799 })
1800}
1801
1802fn raise_live_record(
1803 record: v1::LiveQueryResultRecord,
1804) -> Result<ApplicationRecord, ApplicationClientError> {
1805 Ok(ApplicationRecord {
1806 entity: record.entity,
1807 fields: raise_value_record(record.fields)?,
1808 })
1809}
1810
1811fn raise_value_record(
1812 record: Option<v1::ValueRecord>,
1813) -> Result<BTreeMap<String, ApplicationValue>, ApplicationClientError> {
1814 let mut output = BTreeMap::new();
1815 for field in record
1816 .ok_or(ApplicationClientError::InvalidResponse)?
1817 .fields
1818 {
1819 if field.name.is_empty()
1820 || output
1821 .insert(
1822 field.name,
1823 raise_value(field.value.ok_or(ApplicationClientError::InvalidResponse)?)?,
1824 )
1825 .is_some()
1826 {
1827 return Err(ApplicationClientError::InvalidResponse);
1828 }
1829 }
1830 Ok(output)
1831}
1832
1833fn raise_patch_operation(
1834 operation: v1::LiveQueryPatchOperation,
1835) -> Result<LiveQueryPatchOperation, ApplicationClientError> {
1836 match operation
1837 .operation
1838 .ok_or(ApplicationClientError::InvalidResponse)?
1839 {
1840 v1::live_query_patch_operation::Operation::Insert(value) => {
1841 Ok(LiveQueryPatchOperation::Insert {
1842 index: value.index,
1843 record: raise_live_record(
1844 value
1845 .record
1846 .ok_or(ApplicationClientError::InvalidResponse)?,
1847 )?,
1848 })
1849 }
1850 v1::live_query_patch_operation::Operation::Remove(value) => {
1851 Ok(LiveQueryPatchOperation::Remove {
1852 index: value.index,
1853 key: raise_value_record(value.key)?,
1854 })
1855 }
1856 v1::live_query_patch_operation::Operation::Replace(value) => {
1857 Ok(LiveQueryPatchOperation::Replace {
1858 index: value.index,
1859 record: raise_live_record(
1860 value
1861 .record
1862 .ok_or(ApplicationClientError::InvalidResponse)?,
1863 )?,
1864 })
1865 }
1866 v1::live_query_patch_operation::Operation::Move(value) => {
1867 Ok(LiveQueryPatchOperation::Move {
1868 from: value.from,
1869 to: value.to,
1870 key: raise_value_record(value.key)?,
1871 })
1872 }
1873 }
1874}
1875
1876#[cfg(test)]
1877mod tests {
1878 use super::*;
1879
1880 #[test]
1881 fn consumer_names_and_limits_fail_closed() {
1882 let operation = ApplicationReactiveOperation::new([1; 32], "Events", BTreeMap::new())
1883 .expect("operation");
1884 assert!(ApplicationEventConsumer::new(operation.clone(), "worker_1").is_ok());
1885 assert!(ApplicationEventConsumer::new(operation, "not valid").is_err());
1886 assert!(
1887 EventConsumerOptions {
1888 batch_limit: 65,
1889 ..EventConsumerOptions::default()
1890 }
1891 .validate()
1892 .is_err()
1893 );
1894 }
1895
1896 #[test]
1897 fn cursor_is_opaque_and_bounded() {
1898 assert!(LiveQueryCursor::new(Vec::new()).is_err());
1899 assert!(LiveQueryCursor::new(vec![7; 16_385]).is_err());
1900 assert_eq!(
1901 LiveQueryCursor::new(vec![7; 32])
1902 .expect("cursor")
1903 .as_bytes(),
1904 &[7; 32]
1905 );
1906 let cursor = LiveQueryCursor::new(vec![1, 2, 3, 4]).expect("cursor");
1907 assert_eq!(cursor.to_standard_base64(), "AQIDBA==");
1908 assert_eq!(
1909 LiveQueryCursor::from_standard_base64("AQIDBA==")
1910 .expect("canonical Base64")
1911 .as_bytes(),
1912 &[1, 2, 3, 4]
1913 );
1914 for invalid in ["AQIDBA", "AQIDBA=", "AQIDBA__", "AZ==", "A==="] {
1915 assert!(LiveQueryCursor::from_standard_base64(invalid).is_err());
1916 }
1917 }
1918
1919 #[test]
1920 fn selected_event_fields_reject_duplicates() {
1921 let event = v1::ConsumedEvent {
1922 event: Some(v1::SymbolicEvent {
1923 event_id: Some(v1::EventId {
1924 commit_sequence: 1,
1925 event_ordinal: 0,
1926 }),
1927 event_name: "Changed".to_owned(),
1928 writer_contract_version: 1,
1929 command_name: "Change".to_owned(),
1930 actor_kind: v1::ActorKind::Service as i32,
1931 provenance_uri: "riffdb://provenance/x".to_owned(),
1932 history_incarnation: 1,
1933 fields: vec![
1934 v1::SymbolicEventField {
1935 name: "value".to_owned(),
1936 value: Some(v1::Value {
1937 kind: Some(v1::value::Kind::I64Value(1)),
1938 }),
1939 },
1940 v1::SymbolicEventField {
1941 name: "value".to_owned(),
1942 value: Some(v1::Value {
1943 kind: Some(v1::value::Kind::I64Value(2)),
1944 }),
1945 },
1946 ],
1947 ..v1::SymbolicEvent::default()
1948 }),
1949 attempt: 1,
1950 lease_token: vec![1; 32],
1951 expires_at: Some(v1::Timestamp {
1952 seconds: 1,
1953 nanos: 0,
1954 }),
1955 };
1956 assert!(matches!(
1957 raise_event_delivery(event),
1958 Err(ApplicationClientError::InvalidResponse)
1959 ));
1960 }
1961}