1use crate::comments::{Thread, ThreadItem, UserSummary};
2use crate::events::Event;
3use crate::labels::LabelValidationError;
4use crate::notebooks::front_matter::FrontMatterValidationError;
5use crate::notebooks::operations::Operation;
6use crate::timestamps::Timestamp;
7use base64uuid::Base64Uuid;
8#[cfg(feature = "fp-bindgen")]
9use fp_bindgen::prelude::*;
10use serde::{Deserialize, Serialize};
11use std::cmp::Ordering;
12use std::fmt::Debug;
13use typed_builder::TypedBuilder;
14
15#[derive(Clone, Debug, Deserialize, Serialize)]
17#[cfg_attr(
18 feature = "fp-bindgen",
19 derive(Serializable),
20 fp(rust_module = "fiberplane_models::realtime")
21)]
22#[non_exhaustive]
23#[serde(tag = "type", rename_all = "snake_case")]
24pub enum ClientRealtimeMessage {
25 Authenticate(AuthenticateMessage),
27
28 Subscribe(SubscribeMessage),
30
31 Unsubscribe(UnsubscribeMessage),
33
34 ApplyOperation(Box<ApplyOperationMessage>),
36
37 ApplyOperationBatch(Box<ApplyOperationBatchMessage>),
39
40 DebugRequest(DebugRequestMessage),
42
43 FocusInfo(FocusInfoMessage),
44
45 UserTypingComment(UserTypingCommentClientMessage),
47
48 SubscribeWorkspace(SubscribeWorkspaceMessage),
50
51 UnsubscribeWorkspace(UnsubscribeWorkspaceMessage),
53}
54
55impl ClientRealtimeMessage {
56 pub fn op_id(&self) -> &Option<String> {
57 use ClientRealtimeMessage::*;
58 match self {
59 Authenticate(msg) => &msg.op_id,
60 Subscribe(msg) => &msg.op_id,
61 Unsubscribe(msg) => &msg.op_id,
62 ApplyOperation(msg) => &msg.op_id,
63 ApplyOperationBatch(msg) => &msg.op_id,
64 DebugRequest(msg) => &msg.op_id,
65 FocusInfo(msg) => &msg.op_id,
66 UserTypingComment(msg) => &msg.op_id,
67 SubscribeWorkspace(msg) => &msg.op_id,
68 UnsubscribeWorkspace(msg) => &msg.op_id,
69 }
70 }
71}
72
73#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
75#[cfg_attr(
76 feature = "fp-bindgen",
77 derive(Serializable),
78 fp(rust_module = "fiberplane_models::realtime")
79)]
80#[non_exhaustive]
81#[serde(tag = "type", rename_all = "snake_case")]
82pub enum ServerRealtimeMessage {
83 ApplyOperation(Box<ApplyOperationMessage>),
85
86 Ack(AckMessage),
89
90 Err(ErrMessage),
93
94 DebugResponse(DebugResponseMessage),
97
98 EventAdded(EventAddedMessage),
100
101 EventUpdated(EventUpdatedMessage),
103
104 EventDeleted(EventDeletedMessage),
106
107 Mention(MentionMessage),
110
111 Rejected(RejectedMessage),
114
115 SubscriberAdded(SubscriberAddedMessage),
117
118 SubscriberRemoved(SubscriberRemovedMessage),
120
121 SubscriberChangedFocus(SubscriberChangedFocusMessage),
122
123 ThreadAdded(ThreadAddedMessage),
125
126 ThreadItemAdded(ThreadItemAddedMessage),
128
129 ThreadItemUpdated(ThreadItemUpdatedMessage),
131
132 ThreadDeleted(ThreadDeletedMessage),
134
135 UserTypingComment(UserTypingCommentServerMessage),
137}
138
139#[derive(Clone, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
140#[cfg_attr(
141 feature = "fp-bindgen",
142 derive(Serializable),
143 fp(rust_module = "fiberplane_models::realtime")
144)]
145#[non_exhaustive]
146#[serde(rename_all = "camelCase")]
147pub struct AuthenticateMessage {
148 #[builder(setter(into))]
150 pub token: String,
151
152 #[builder(default, setter(into, strip_option))]
157 #[serde(default, skip_serializing_if = "Option::is_none")]
158 pub op_id: Option<String>,
159}
160
161impl Debug for AuthenticateMessage {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 f.debug_struct("AuthenticateMessage")
164 .field("token", &"[REDACTED]")
165 .field("op_id", &self.op_id)
166 .finish()
167 }
168}
169
170#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
171#[cfg_attr(
172 feature = "fp-bindgen",
173 derive(Serializable),
174 fp(rust_module = "fiberplane_models::realtime")
175)]
176#[non_exhaustive]
177#[serde(rename_all = "camelCase")]
178pub struct SubscribeMessage {
179 #[builder(setter(into))]
181 pub notebook_id: String,
182
183 #[builder(default, setter(into, strip_option))]
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub revision: Option<u32>,
189
190 #[builder(default, setter(into, strip_option))]
195 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub op_id: Option<String>,
197}
198
199#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
200#[cfg_attr(
201 feature = "fp-bindgen",
202 derive(Serializable),
203 fp(rust_module = "fiberplane_models::realtime")
204)]
205#[non_exhaustive]
206#[serde(rename_all = "camelCase")]
207pub struct UnsubscribeMessage {
208 #[builder(setter(into))]
210 pub notebook_id: String,
211
212 #[builder(default, setter(into, strip_option))]
217 #[serde(default, skip_serializing_if = "Option::is_none")]
218 pub op_id: Option<String>,
219}
220
221#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TypedBuilder)]
222#[cfg_attr(
223 feature = "fp-bindgen",
224 derive(Serializable),
225 fp(rust_module = "fiberplane_models::realtime")
226)]
227#[non_exhaustive]
228#[serde(rename_all = "camelCase")]
229pub struct ApplyOperationMessage {
230 #[builder(setter(into))]
232 pub notebook_id: String,
233
234 pub operation: Operation,
236
237 pub revision: u32,
245
246 #[builder(default, setter(into, strip_option))]
251 #[serde(default, skip_serializing_if = "Option::is_none")]
252 pub op_id: Option<String>,
253}
254
255#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TypedBuilder)]
256#[cfg_attr(
257 feature = "fp-bindgen",
258 derive(Serializable),
259 fp(rust_module = "fiberplane_models::realtime")
260)]
261#[non_exhaustive]
262#[serde(rename_all = "camelCase")]
263pub struct ApplyOperationBatchMessage {
264 #[builder(setter(into))]
266 pub notebook_id: String,
267
268 pub operations: Vec<Operation>,
270
271 pub revision: u32,
279
280 #[builder(default, setter(into, strip_option))]
285 #[serde(default, skip_serializing_if = "Option::is_none")]
286 pub op_id: Option<String>,
287}
288
289#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
294#[cfg_attr(
295 feature = "fp-bindgen",
296 derive(Serializable),
297 fp(rust_module = "fiberplane_models::realtime")
298)]
299#[non_exhaustive]
300#[serde(rename_all = "camelCase")]
301pub struct AckMessage {
302 pub op_id: String,
305}
306
307impl AckMessage {
308 pub fn new(op_id: String) -> Self {
309 Self { op_id }
310 }
311}
312
313#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
314#[cfg_attr(
315 feature = "fp-bindgen",
316 derive(Serializable),
317 fp(rust_module = "fiberplane_models::realtime")
318)]
319#[non_exhaustive]
320#[serde(rename_all = "camelCase")]
321pub struct ErrMessage {
322 pub error_message: String,
324
325 #[serde(default, skip_serializing_if = "Option::is_none")]
330 pub op_id: Option<String>,
331}
332
333impl ErrMessage {
334 pub fn new(message: impl Into<String>) -> Self {
336 Self {
337 error_message: message.into(),
338 op_id: None,
339 }
340 }
341
342 pub fn with_optional_op_id(self, op_id: Option<String>) -> Self {
344 Self { op_id, ..self }
345 }
346}
347
348#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
349#[cfg_attr(
350 feature = "fp-bindgen",
351 derive(Serializable),
352 fp(rust_module = "fiberplane_models::realtime")
353)]
354#[non_exhaustive]
355#[serde(rename_all = "camelCase")]
356pub struct DebugRequestMessage {
357 #[builder(default, setter(into, strip_option))]
362 #[serde(default, skip_serializing_if = "Option::is_none")]
363 pub op_id: Option<String>,
364}
365
366#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
367#[cfg_attr(
368 feature = "fp-bindgen",
369 derive(Serializable),
370 fp(rust_module = "fiberplane_models::realtime")
371)]
372#[non_exhaustive]
373#[serde(rename_all = "camelCase")]
374pub struct DebugResponseMessage {
375 #[builder(setter(into))]
377 pub sid: String,
378
379 #[builder(default)]
381 pub subscribed_notebooks: Vec<String>,
382
383 #[builder(default, setter(into, strip_option))]
388 #[serde(default, skip_serializing_if = "Option::is_none")]
389 pub op_id: Option<String>,
390}
391
392#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
393#[cfg_attr(
394 feature = "fp-bindgen",
395 derive(Serializable),
396 fp(rust_module = "fiberplane_models::realtime")
397)]
398#[non_exhaustive]
399#[serde(rename_all = "camelCase")]
400pub struct EventAddedMessage {
401 #[builder(setter(into))]
403 pub workspace_id: Base64Uuid,
404
405 pub event: Event,
407}
408
409#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
410#[cfg_attr(
411 feature = "fp-bindgen",
412 derive(Serializable),
413 fp(rust_module = "fiberplane_models::realtime")
414)]
415#[non_exhaustive]
416#[serde(rename_all = "camelCase")]
417pub struct EventUpdatedMessage {
418 #[builder(setter(into))]
420 pub workspace_id: Base64Uuid,
421
422 pub event: Event,
424}
425
426#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
427#[cfg_attr(
428 feature = "fp-bindgen",
429 derive(Serializable),
430 fp(rust_module = "fiberplane_models::realtime")
431)]
432#[non_exhaustive]
433#[serde(rename_all = "camelCase")]
434pub struct EventDeletedMessage {
435 #[builder(setter(into))]
437 pub workspace_id: Base64Uuid,
438
439 pub event_id: String,
441}
442
443#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
444#[cfg_attr(
445 feature = "fp-bindgen",
446 derive(Serializable),
447 fp(rust_module = "fiberplane_models::realtime")
448)]
449#[non_exhaustive]
450#[serde(rename_all = "camelCase")]
451pub struct MentionMessage {
452 #[builder(setter(into))]
454 pub notebook_id: String,
455
456 #[builder(setter(into))]
458 pub cell_id: String,
459
460 pub mentioned_by: MentionedBy,
462}
463
464#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
465#[cfg_attr(
466 feature = "fp-bindgen",
467 derive(Serializable),
468 fp(rust_module = "fiberplane_models::realtime")
469)]
470#[non_exhaustive]
471#[serde(rename_all = "camelCase")]
472pub struct MentionedBy {
473 #[builder(setter(into))]
474 pub name: String,
475}
476
477#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
479#[cfg_attr(
480 feature = "fp-bindgen",
481 derive(Serializable),
482 fp(rust_module = "fiberplane_models::realtime")
483)]
484#[non_exhaustive]
485#[serde(rename_all = "camelCase")]
486pub struct RejectedMessage {
487 pub reason: Box<RejectReason>,
489
490 #[serde(default, skip_serializing_if = "Option::is_none")]
495 pub op_id: Option<String>,
496}
497
498impl RejectedMessage {
499 pub fn new(reason: RejectReason, op_id: Option<String>) -> Self {
500 Self {
501 reason: Box::new(reason),
502 op_id,
503 }
504 }
505}
506
507#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
509#[cfg_attr(
510 feature = "fp-bindgen",
511 derive(Serializable),
512 fp(rust_module = "fiberplane_models::realtime")
513)]
514#[non_exhaustive]
515#[serde(tag = "type", rename_all = "snake_case")]
516pub enum RejectReason {
517 CellIndexOutOfBounds,
519
520 #[serde(rename_all = "camelCase")]
522 CellNotFound { cell_id: String },
523
524 #[serde(rename_all = "camelCase")]
526 DuplicateCellId { cell_id: String },
527
528 DuplicateLabel(DuplicateLabelRejectReason),
530
531 #[serde(rename_all = "camelCase")]
533 DuplicateTableId { table_id: String },
534
535 #[serde(rename_all = "camelCase")]
537 FailedPrecondition { message: String },
538
539 InvalidLabel(InvalidLabelRejectReason),
541
542 InconsistentState,
544
545 InvalidTableColumnIndex,
547
548 InvalidTableRowIndex,
550
551 InvalidTableDimensions,
554
555 InvalidTableId { table_id: String },
558
559 #[serde(rename_all = "camelCase")]
561 InconsistentFrontMatter { message: String },
562
563 #[serde(rename_all = "camelCase")]
568 InvalidFrontMatterUpdate(InvalidFrontMatterRejectReason),
569
570 #[serde(rename_all = "camelCase")]
572 NoTableCell { cell_id: String },
573
574 #[serde(rename_all = "camelCase")]
576 NoTextCell { cell_id: String },
577
578 Outdated(OutdatedRejectReason),
580
581 #[serde(rename_all = "camelCase")]
583 UnknownOperation { operation_summary: String },
584}
585
586#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
587#[cfg_attr(
588 feature = "fp-bindgen",
589 derive(Serializable),
590 fp(rust_module = "fiberplane_models::realtime")
591)]
592#[non_exhaustive]
593#[serde(rename_all = "camelCase")]
594pub struct InvalidFrontMatterRejectReason {
595 pub problem_key: String,
597 pub error: FrontMatterValidationError,
599}
600
601#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
602#[cfg_attr(
603 feature = "fp-bindgen",
604 derive(Serializable),
605 fp(rust_module = "fiberplane_models::realtime")
606)]
607#[non_exhaustive]
608#[serde(rename_all = "camelCase")]
609pub struct OutdatedRejectReason {
610 pub current_revision: u32,
612}
613
614#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
615#[cfg_attr(
616 feature = "fp-bindgen",
617 derive(Serializable),
618 fp(rust_module = "fiberplane_models::realtime")
619)]
620#[non_exhaustive]
621#[serde(rename_all = "camelCase")]
622pub struct InvalidLabelRejectReason {
623 pub key: String,
625
626 pub validation_error: LabelValidationError,
628}
629
630#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
631#[cfg_attr(
632 feature = "fp-bindgen",
633 derive(Serializable),
634 fp(rust_module = "fiberplane_models::realtime")
635)]
636#[non_exhaustive]
637#[serde(rename_all = "camelCase")]
638pub struct DuplicateLabelRejectReason {
639 pub key: String,
641}
642
643#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
644#[cfg_attr(
645 feature = "fp-bindgen",
646 derive(Serializable),
647 fp(rust_module = "fiberplane_models::realtime")
648)]
649#[non_exhaustive]
650#[serde(rename_all = "camelCase")]
651pub struct SubscriberAddedMessage {
652 #[builder(setter(into))]
654 pub notebook_id: String,
655
656 #[builder(setter(into))]
661 pub session_id: String,
662
663 #[builder(setter(into))]
665 pub created_at: Timestamp,
666
667 #[builder(setter(into))]
669 pub updated_at: Timestamp,
670
671 pub user: User,
673
674 #[builder(default)]
676 #[serde(default)]
677 pub focus: NotebookFocus,
678}
679
680#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
681#[cfg_attr(
682 feature = "fp-bindgen",
683 derive(Serializable),
684 fp(rust_module = "fiberplane_models::realtime")
685)]
686#[non_exhaustive]
687#[serde(rename_all = "camelCase")]
688pub struct SubscriberRemovedMessage {
689 #[builder(setter(into))]
691 pub notebook_id: String,
692
693 #[builder(setter(into))]
695 pub session_id: String,
696}
697
698#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
699#[cfg_attr(
700 feature = "fp-bindgen",
701 derive(Serializable),
702 fp(rust_module = "fiberplane_models::realtime")
703)]
704#[non_exhaustive]
705#[serde(rename_all = "camelCase")]
706pub struct User {
707 #[builder(setter(into))]
710 pub id: String,
711
712 #[builder(setter(into))]
714 pub name: String,
715}
716
717#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
718#[cfg_attr(
719 feature = "fp-bindgen",
720 derive(Serializable),
721 fp(rust_module = "fiberplane_models::realtime")
722)]
723#[non_exhaustive]
724#[serde(rename_all = "camelCase")]
725pub struct FocusInfoMessage {
726 #[builder(setter(into))]
728 pub notebook_id: String,
729
730 #[builder(default)]
732 #[serde(default)]
733 pub focus: NotebookFocus,
734
735 #[builder(default, setter(into, strip_option))]
740 #[serde(default, skip_serializing_if = "Option::is_none")]
741 pub op_id: Option<String>,
742}
743
744#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
745#[cfg_attr(
746 feature = "fp-bindgen",
747 derive(Serializable),
748 fp(rust_module = "fiberplane_models::realtime")
749)]
750#[non_exhaustive]
751#[serde(rename_all = "camelCase")]
752pub struct UserTypingCommentClientMessage {
753 #[builder(setter(into))]
754 pub notebook_id: Base64Uuid,
755
756 #[builder(setter(into))]
757 pub thread_id: Base64Uuid,
758
759 #[builder(default, setter(into, strip_option))]
764 #[serde(default, skip_serializing_if = "Option::is_none")]
765 pub op_id: Option<String>,
766}
767
768#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
769#[cfg_attr(
770 feature = "fp-bindgen",
771 derive(Serializable),
772 fp(rust_module = "fiberplane_models::realtime")
773)]
774#[non_exhaustive]
775#[serde(rename_all = "camelCase")]
776pub struct SubscribeWorkspaceMessage {
777 #[builder(setter(into))]
779 pub workspace_id: Base64Uuid,
780
781 #[builder(default, setter(into, strip_option))]
786 #[serde(default, skip_serializing_if = "Option::is_none")]
787 pub op_id: Option<String>,
788}
789
790#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
791#[cfg_attr(
792 feature = "fp-bindgen",
793 derive(Serializable),
794 fp(rust_module = "fiberplane_models::realtime")
795)]
796#[non_exhaustive]
797#[serde(rename_all = "camelCase")]
798pub struct UnsubscribeWorkspaceMessage {
799 #[builder(setter(into))]
801 pub workspace_id: Base64Uuid,
802
803 #[builder(default, setter(into, strip_option))]
808 #[serde(default, skip_serializing_if = "Option::is_none")]
809 pub op_id: Option<String>,
810}
811
812#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
813#[cfg_attr(
814 feature = "fp-bindgen",
815 derive(Serializable),
816 fp(rust_module = "fiberplane_models::realtime")
817)]
818#[non_exhaustive]
819#[serde(rename_all = "camelCase")]
820pub struct SubscriberChangedFocusMessage {
821 #[builder(setter(into))]
823 pub session_id: String,
824
825 #[builder(setter(into))]
827 pub notebook_id: String,
828
829 #[serde(default)]
831 pub focus: NotebookFocus,
832
833 #[builder(setter(into))]
834 pub updated_at: Timestamp,
835}
836
837#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
843#[cfg_attr(
844 feature = "fp-bindgen",
845 derive(Serializable),
846 fp(rust_module = "fiberplane_models::realtime")
847)]
848#[non_exhaustive]
849#[serde(rename_all = "camelCase")]
850pub struct FocusPosition {
851 #[builder(setter(into))]
856 pub cell_id: String,
857
858 #[serde(default, skip_serializing_if = "Option::is_none")]
866 pub field: Option<String>,
867
868 #[serde(default, skip_serializing_if = "Option::is_none")]
871 pub offset: Option<u32>,
872}
873
874#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
876#[cfg_attr(
877 feature = "fp-bindgen",
878 derive(Serializable),
879 fp(rust_module = "fiberplane_models::realtime")
880)]
881#[non_exhaustive]
882#[serde(rename_all = "snake_case", tag = "type")]
883pub enum NotebookFocus {
884 #[default]
886 None,
887 Collapsed(FocusPosition),
890 Selection {
893 anchor: FocusPosition,
894 focus: FocusPosition,
895 },
896}
897
898impl NotebookFocus {
899 pub fn anchor_cell_id(&self) -> Option<&str> {
900 match self {
901 Self::None => None,
902 Self::Collapsed(collapsed) => Some(&collapsed.cell_id),
903 Self::Selection { anchor, .. } => Some(&anchor.cell_id),
904 }
905 }
906
907 pub fn anchor_cell_index(&self, cell_ids: &[&str]) -> Option<usize> {
908 cell_ids
909 .iter()
910 .position(|cell_id| Some(*cell_id) == self.anchor_cell_id())
911 }
912
913 pub fn anchor_field(&self) -> Option<&str> {
914 match self {
915 Self::None => None,
916 Self::Collapsed(collapsed) => collapsed.field.as_deref(),
917 Self::Selection { anchor, .. } => anchor.field.as_deref(),
918 }
919 }
920
921 pub fn anchor_offset(&self) -> u32 {
922 match self {
923 Self::None => 0,
924 Self::Collapsed(position) => position.offset.unwrap_or_default(),
925 Self::Selection { anchor, .. } => anchor.offset.unwrap_or_default(),
926 }
927 }
928
929 pub fn anchor_position(&self) -> Option<&FocusPosition> {
930 match self {
931 Self::None => None,
932 Self::Collapsed(position) => Some(position),
933 Self::Selection { anchor, .. } => Some(anchor),
934 }
935 }
936
937 pub fn end_cell_id(&self, cell_ids: &[&str]) -> Option<&str> {
938 match self {
939 Self::None => None,
940 Self::Collapsed(position) => Some(&position.cell_id),
941 Self::Selection { anchor, focus } => {
942 let anchor_cell_index = self.anchor_cell_index(cell_ids).unwrap_or_default();
943 let focus_cell_index = self.focus_cell_index(cell_ids).unwrap_or_default();
944 if anchor_cell_index > focus_cell_index {
945 Some(&anchor.cell_id)
946 } else {
947 Some(&focus.cell_id)
948 }
949 }
950 }
951 }
952
953 pub fn end_offset(&self, cell_ids: &[&str]) -> u32 {
954 match self {
955 Self::None => 0,
956 Self::Collapsed(position) => position.offset.unwrap_or_default(),
957 Self::Selection { anchor, focus } => {
958 let anchor_cell_index = self.anchor_cell_index(cell_ids).unwrap_or_default();
959 let anchor_offset = anchor.offset.unwrap_or_default();
960 let focus_cell_index = self.focus_cell_index(cell_ids).unwrap_or_default();
961 let focus_offset = focus.offset.unwrap_or_default();
962 match anchor_cell_index.cmp(&focus_cell_index) {
963 Ordering::Greater => anchor_offset,
964 Ordering::Equal => std::cmp::max(anchor_offset, focus_offset),
965 Ordering::Less => focus_offset,
966 }
967 }
968 }
969 }
970
971 pub fn focus_cell_id(&self) -> Option<&str> {
972 match self {
973 Self::None => None,
974 Self::Collapsed(collapsed) => Some(&collapsed.cell_id),
975 Self::Selection { focus, .. } => Some(&focus.cell_id),
976 }
977 }
978
979 pub fn focus_cell_index(&self, cell_ids: &[&str]) -> Option<usize> {
980 cell_ids
981 .iter()
982 .position(|cell_id| Some(*cell_id) == self.focus_cell_id())
983 }
984
985 pub fn focus_field(&self) -> Option<&str> {
986 match self {
987 Self::None => None,
988 Self::Collapsed(collapsed) => collapsed.field.as_deref(),
989 Self::Selection { focus, .. } => focus.field.as_deref(),
990 }
991 }
992
993 pub fn focus_offset(&self) -> u32 {
994 match self {
995 Self::None => 0,
996 Self::Collapsed(position) => position.offset.unwrap_or_default(),
997 Self::Selection { focus, .. } => focus.offset.unwrap_or_default(),
998 }
999 }
1000
1001 pub fn focus_position(&self) -> Option<&FocusPosition> {
1002 match self {
1003 Self::None => None,
1004 Self::Collapsed(position) => Some(position),
1005 Self::Selection { focus, .. } => Some(focus),
1006 }
1007 }
1008
1009 pub fn has_selection(&self) -> bool {
1010 !self.is_collapsed()
1011 }
1012
1013 pub fn is_collapsed(&self) -> bool {
1016 match self {
1017 Self::None | Self::Collapsed(_) => true,
1018 Self::Selection { focus, anchor } => *focus == *anchor,
1019 }
1020 }
1021
1022 pub fn is_none(&self) -> bool {
1023 matches!(self, Self::None)
1024 }
1025
1026 pub fn start_cell_id(&self, cell_ids: &[&str]) -> Option<&str> {
1027 match self {
1028 Self::None => None,
1029 Self::Collapsed(position) => Some(&position.cell_id),
1030 Self::Selection { anchor, focus } => {
1031 if self.anchor_cell_index(cell_ids).unwrap_or_default()
1032 < self.focus_cell_index(cell_ids).unwrap_or_default()
1033 {
1034 Some(&anchor.cell_id)
1035 } else {
1036 Some(&focus.cell_id)
1037 }
1038 }
1039 }
1040 }
1041
1042 pub fn start_offset(&self, cell_ids: &[&str]) -> u32 {
1043 match self {
1044 Self::None => 0,
1045 Self::Collapsed(position) => position.offset.unwrap_or_default(),
1046 Self::Selection { anchor, focus } => {
1047 let anchor_cell_index = self.anchor_cell_index(cell_ids).unwrap_or_default();
1048 let anchor_offset = anchor.offset.unwrap_or_default();
1049 let focus_cell_index = self.focus_cell_index(cell_ids).unwrap_or_default();
1050 let focus_offset = focus.offset.unwrap_or_default();
1051 match anchor_cell_index.cmp(&focus_cell_index) {
1052 Ordering::Less => anchor_offset,
1053 Ordering::Equal => std::cmp::min(anchor_offset, focus_offset),
1054 Ordering::Greater => focus_offset,
1055 }
1056 }
1057 }
1058 }
1059}
1060
1061#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
1062#[cfg_attr(
1063 feature = "fp-bindgen",
1064 derive(Serializable),
1065 fp(rust_module = "fiberplane_models::realtime")
1066)]
1067#[non_exhaustive]
1068#[serde(rename_all = "camelCase")]
1069pub struct ThreadAddedMessage {
1070 #[builder(setter(into))]
1071 pub notebook_id: String,
1072
1073 pub thread: Thread,
1074}
1075
1076#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
1077#[cfg_attr(
1078 feature = "fp-bindgen",
1079 derive(Serializable),
1080 fp(rust_module = "fiberplane_models::realtime")
1081)]
1082#[non_exhaustive]
1083#[serde(rename_all = "camelCase")]
1084pub struct ThreadItemAddedMessage {
1085 #[builder(setter(into))]
1086 pub notebook_id: String,
1087
1088 #[builder(setter(into))]
1089 pub thread_id: String,
1090
1091 pub thread_item: ThreadItem,
1092}
1093
1094#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
1095#[cfg_attr(
1096 feature = "fp-bindgen",
1097 derive(Serializable),
1098 fp(rust_module = "fiberplane_models::realtime")
1099)]
1100#[non_exhaustive]
1101#[serde(rename_all = "camelCase")]
1102pub struct ThreadItemUpdatedMessage {
1103 #[builder(setter(into))]
1104 pub notebook_id: String,
1105
1106 #[builder(setter(into))]
1107 pub thread_id: String,
1108
1109 pub thread_item: ThreadItem,
1110}
1111
1112#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
1113#[cfg_attr(
1114 feature = "fp-bindgen",
1115 derive(Serializable),
1116 fp(rust_module = "fiberplane_models::realtime")
1117)]
1118#[non_exhaustive]
1119#[serde(rename_all = "camelCase")]
1120pub struct ThreadDeletedMessage {
1121 #[builder(setter(into))]
1122 pub notebook_id: String,
1123
1124 #[builder(setter(into))]
1125 pub thread_id: String,
1126}
1127
1128#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
1129#[cfg_attr(
1130 feature = "fp-bindgen",
1131 derive(Serializable),
1132 fp(rust_module = "fiberplane_models::realtime")
1133)]
1134#[non_exhaustive]
1135#[serde(rename_all = "camelCase")]
1136pub struct UserTypingCommentServerMessage {
1137 #[builder(setter(into))]
1138 pub notebook_id: String,
1139
1140 #[builder(setter(into))]
1141 pub thread_id: String,
1142
1143 pub user: UserSummary,
1144
1145 #[builder(setter(into))]
1146 pub updated_at: Timestamp,
1147}
1148
1149#[cfg(test)]
1150mod tests {
1151 use super::*;
1152
1153 #[test]
1154 fn serialize_reject_reason() {
1155 let reason = OutdatedRejectReason {
1156 current_revision: 1,
1157 };
1158 let reason = RejectReason::Outdated(reason);
1159 let result = serde_json::to_string(&reason);
1160 if let Err(err) = result {
1161 panic!("Unexpected error occurred: {err:?}");
1162 }
1163 }
1164}