use crate::comments::{Thread, ThreadItem, UserSummary};
use crate::events::Event;
use crate::labels::LabelValidationError;
use crate::notebooks::front_matter::FrontMatterValidationError;
use crate::notebooks::operations::Operation;
use crate::timestamps::Timestamp;
use base64uuid::Base64Uuid;
#[cfg(feature = "fp-bindgen")]
use fp_bindgen::prelude::*;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::Debug;
use typed_builder::TypedBuilder;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ClientRealtimeMessage {
Authenticate(AuthenticateMessage),
Subscribe(SubscribeMessage),
Unsubscribe(UnsubscribeMessage),
ApplyOperation(Box<ApplyOperationMessage>),
ApplyOperationBatch(Box<ApplyOperationBatchMessage>),
DebugRequest(DebugRequestMessage),
FocusInfo(FocusInfoMessage),
UserTypingComment(UserTypingCommentClientMessage),
SubscribeWorkspace(SubscribeWorkspaceMessage),
UnsubscribeWorkspace(UnsubscribeWorkspaceMessage),
}
impl ClientRealtimeMessage {
pub fn op_id(&self) -> &Option<String> {
use ClientRealtimeMessage::*;
match self {
Authenticate(msg) => &msg.op_id,
Subscribe(msg) => &msg.op_id,
Unsubscribe(msg) => &msg.op_id,
ApplyOperation(msg) => &msg.op_id,
ApplyOperationBatch(msg) => &msg.op_id,
DebugRequest(msg) => &msg.op_id,
FocusInfo(msg) => &msg.op_id,
UserTypingComment(msg) => &msg.op_id,
SubscribeWorkspace(msg) => &msg.op_id,
UnsubscribeWorkspace(msg) => &msg.op_id,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ServerRealtimeMessage {
ApplyOperation(Box<ApplyOperationMessage>),
Ack(AckMessage),
Err(ErrMessage),
DebugResponse(DebugResponseMessage),
EventAdded(EventAddedMessage),
EventUpdated(EventUpdatedMessage),
EventDeleted(EventDeletedMessage),
Mention(MentionMessage),
Rejected(RejectedMessage),
SubscriberAdded(SubscriberAddedMessage),
SubscriberRemoved(SubscriberRemovedMessage),
SubscriberChangedFocus(SubscriberChangedFocusMessage),
ThreadAdded(ThreadAddedMessage),
ThreadItemAdded(ThreadItemAddedMessage),
ThreadItemUpdated(ThreadItemUpdatedMessage),
ThreadDeleted(ThreadDeletedMessage),
UserTypingComment(UserTypingCommentServerMessage),
}
#[derive(Clone, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct AuthenticateMessage {
#[builder(setter(into))]
pub token: String,
#[builder(default, setter(into, strip_option))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
impl Debug for AuthenticateMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AuthenticateMessage")
.field("token", &"[REDACTED]")
.field("op_id", &self.op_id)
.finish()
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct SubscribeMessage {
#[builder(setter(into))]
pub notebook_id: String,
#[builder(default, setter(into, strip_option))]
#[serde(skip_serializing_if = "Option::is_none")]
pub revision: Option<u32>,
#[builder(default, setter(into, strip_option))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct UnsubscribeMessage {
#[builder(setter(into))]
pub notebook_id: String,
#[builder(default, setter(into, strip_option))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct ApplyOperationMessage {
#[builder(setter(into))]
pub notebook_id: String,
pub operation: Operation,
pub revision: u32,
#[builder(default, setter(into, strip_option))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct ApplyOperationBatchMessage {
#[builder(setter(into))]
pub notebook_id: String,
pub operations: Vec<Operation>,
pub revision: u32,
#[builder(default, setter(into, strip_option))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct AckMessage {
pub op_id: String,
}
impl AckMessage {
pub fn new(op_id: String) -> Self {
Self { op_id }
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct ErrMessage {
pub error_message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
impl ErrMessage {
pub fn new(message: impl Into<String>) -> Self {
Self {
error_message: message.into(),
op_id: None,
}
}
pub fn with_optional_op_id(self, op_id: Option<String>) -> Self {
Self { op_id, ..self }
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct DebugRequestMessage {
#[builder(default, setter(into, strip_option))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct DebugResponseMessage {
#[builder(setter(into))]
pub sid: String,
#[builder(default)]
pub subscribed_notebooks: Vec<String>,
#[builder(default, setter(into, strip_option))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct EventAddedMessage {
#[builder(setter(into))]
pub workspace_id: Base64Uuid,
pub event: Event,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct EventUpdatedMessage {
#[builder(setter(into))]
pub workspace_id: Base64Uuid,
pub event: Event,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct EventDeletedMessage {
#[builder(setter(into))]
pub workspace_id: Base64Uuid,
pub event_id: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct MentionMessage {
#[builder(setter(into))]
pub notebook_id: String,
#[builder(setter(into))]
pub cell_id: String,
pub mentioned_by: MentionedBy,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct MentionedBy {
#[builder(setter(into))]
pub name: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct RejectedMessage {
pub reason: Box<RejectReason>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
impl RejectedMessage {
pub fn new(reason: RejectReason, op_id: Option<String>) -> Self {
Self {
reason: Box::new(reason),
op_id,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum RejectReason {
CellIndexOutOfBounds,
#[serde(rename_all = "camelCase")]
CellNotFound { cell_id: String },
#[serde(rename_all = "camelCase")]
DuplicateCellId { cell_id: String },
DuplicateLabel(DuplicateLabelRejectReason),
#[serde(rename_all = "camelCase")]
DuplicateTableId { table_id: String },
#[serde(rename_all = "camelCase")]
FailedPrecondition { message: String },
InvalidLabel(InvalidLabelRejectReason),
InconsistentState,
InvalidTableColumnIndex,
InvalidTableRowIndex,
InvalidTableDimensions,
InvalidTableId { table_id: String },
#[serde(rename_all = "camelCase")]
InconsistentFrontMatter { message: String },
#[serde(rename_all = "camelCase")]
InvalidFrontMatterUpdate(InvalidFrontMatterRejectReason),
#[serde(rename_all = "camelCase")]
NoTableCell { cell_id: String },
#[serde(rename_all = "camelCase")]
NoTextCell { cell_id: String },
Outdated(OutdatedRejectReason),
#[serde(rename_all = "camelCase")]
UnknownOperation { operation_summary: String },
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct InvalidFrontMatterRejectReason {
pub problem_key: String,
pub error: FrontMatterValidationError,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct OutdatedRejectReason {
pub current_revision: u32,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct InvalidLabelRejectReason {
pub key: String,
pub validation_error: LabelValidationError,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct DuplicateLabelRejectReason {
pub key: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct SubscriberAddedMessage {
#[builder(setter(into))]
pub notebook_id: String,
#[builder(setter(into))]
pub session_id: String,
#[builder(setter(into))]
pub created_at: Timestamp,
#[builder(setter(into))]
pub updated_at: Timestamp,
pub user: User,
#[builder(default)]
#[serde(default)]
pub focus: NotebookFocus,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct SubscriberRemovedMessage {
#[builder(setter(into))]
pub notebook_id: String,
#[builder(setter(into))]
pub session_id: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct User {
#[builder(setter(into))]
pub id: String,
#[builder(setter(into))]
pub name: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct FocusInfoMessage {
#[builder(setter(into))]
pub notebook_id: String,
#[builder(default)]
#[serde(default)]
pub focus: NotebookFocus,
#[builder(default, setter(into, strip_option))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct UserTypingCommentClientMessage {
#[builder(setter(into))]
pub notebook_id: Base64Uuid,
#[builder(setter(into))]
pub thread_id: Base64Uuid,
#[builder(default, setter(into, strip_option))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct SubscribeWorkspaceMessage {
#[builder(setter(into))]
pub workspace_id: Base64Uuid,
#[builder(default, setter(into, strip_option))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct UnsubscribeWorkspaceMessage {
#[builder(setter(into))]
pub workspace_id: Base64Uuid,
#[builder(default, setter(into, strip_option))]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct SubscriberChangedFocusMessage {
#[builder(setter(into))]
pub session_id: String,
#[builder(setter(into))]
pub notebook_id: String,
#[serde(default)]
pub focus: NotebookFocus,
#[builder(setter(into))]
pub updated_at: Timestamp,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct FocusPosition {
#[builder(setter(into))]
pub cell_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub field: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub offset: Option<u32>,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum NotebookFocus {
#[default]
None,
Collapsed(FocusPosition),
Selection {
anchor: FocusPosition,
focus: FocusPosition,
},
}
impl NotebookFocus {
pub fn anchor_cell_id(&self) -> Option<&str> {
match self {
Self::None => None,
Self::Collapsed(collapsed) => Some(&collapsed.cell_id),
Self::Selection { anchor, .. } => Some(&anchor.cell_id),
}
}
pub fn anchor_cell_index(&self, cell_ids: &[&str]) -> Option<usize> {
cell_ids
.iter()
.position(|cell_id| Some(*cell_id) == self.anchor_cell_id())
}
pub fn anchor_field(&self) -> Option<&str> {
match self {
Self::None => None,
Self::Collapsed(collapsed) => collapsed.field.as_deref(),
Self::Selection { anchor, .. } => anchor.field.as_deref(),
}
}
pub fn anchor_offset(&self) -> u32 {
match self {
Self::None => 0,
Self::Collapsed(position) => position.offset.unwrap_or_default(),
Self::Selection { anchor, .. } => anchor.offset.unwrap_or_default(),
}
}
pub fn anchor_position(&self) -> Option<&FocusPosition> {
match self {
Self::None => None,
Self::Collapsed(position) => Some(position),
Self::Selection { anchor, .. } => Some(anchor),
}
}
pub fn end_cell_id(&self, cell_ids: &[&str]) -> Option<&str> {
match self {
Self::None => None,
Self::Collapsed(position) => Some(&position.cell_id),
Self::Selection { anchor, focus } => {
let anchor_cell_index = self.anchor_cell_index(cell_ids).unwrap_or_default();
let focus_cell_index = self.focus_cell_index(cell_ids).unwrap_or_default();
if anchor_cell_index > focus_cell_index {
Some(&anchor.cell_id)
} else {
Some(&focus.cell_id)
}
}
}
}
pub fn end_offset(&self, cell_ids: &[&str]) -> u32 {
match self {
Self::None => 0,
Self::Collapsed(position) => position.offset.unwrap_or_default(),
Self::Selection { anchor, focus } => {
let anchor_cell_index = self.anchor_cell_index(cell_ids).unwrap_or_default();
let anchor_offset = anchor.offset.unwrap_or_default();
let focus_cell_index = self.focus_cell_index(cell_ids).unwrap_or_default();
let focus_offset = focus.offset.unwrap_or_default();
match anchor_cell_index.cmp(&focus_cell_index) {
Ordering::Greater => anchor_offset,
Ordering::Equal => std::cmp::max(anchor_offset, focus_offset),
Ordering::Less => focus_offset,
}
}
}
}
pub fn focus_cell_id(&self) -> Option<&str> {
match self {
Self::None => None,
Self::Collapsed(collapsed) => Some(&collapsed.cell_id),
Self::Selection { focus, .. } => Some(&focus.cell_id),
}
}
pub fn focus_cell_index(&self, cell_ids: &[&str]) -> Option<usize> {
cell_ids
.iter()
.position(|cell_id| Some(*cell_id) == self.focus_cell_id())
}
pub fn focus_field(&self) -> Option<&str> {
match self {
Self::None => None,
Self::Collapsed(collapsed) => collapsed.field.as_deref(),
Self::Selection { focus, .. } => focus.field.as_deref(),
}
}
pub fn focus_offset(&self) -> u32 {
match self {
Self::None => 0,
Self::Collapsed(position) => position.offset.unwrap_or_default(),
Self::Selection { focus, .. } => focus.offset.unwrap_or_default(),
}
}
pub fn focus_position(&self) -> Option<&FocusPosition> {
match self {
Self::None => None,
Self::Collapsed(position) => Some(position),
Self::Selection { focus, .. } => Some(focus),
}
}
pub fn has_selection(&self) -> bool {
!self.is_collapsed()
}
pub fn is_collapsed(&self) -> bool {
match self {
Self::None | Self::Collapsed(_) => true,
Self::Selection { focus, anchor } => *focus == *anchor,
}
}
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}
pub fn start_cell_id(&self, cell_ids: &[&str]) -> Option<&str> {
match self {
Self::None => None,
Self::Collapsed(position) => Some(&position.cell_id),
Self::Selection { anchor, focus } => {
if self.anchor_cell_index(cell_ids).unwrap_or_default()
< self.focus_cell_index(cell_ids).unwrap_or_default()
{
Some(&anchor.cell_id)
} else {
Some(&focus.cell_id)
}
}
}
}
pub fn start_offset(&self, cell_ids: &[&str]) -> u32 {
match self {
Self::None => 0,
Self::Collapsed(position) => position.offset.unwrap_or_default(),
Self::Selection { anchor, focus } => {
let anchor_cell_index = self.anchor_cell_index(cell_ids).unwrap_or_default();
let anchor_offset = anchor.offset.unwrap_or_default();
let focus_cell_index = self.focus_cell_index(cell_ids).unwrap_or_default();
let focus_offset = focus.offset.unwrap_or_default();
match anchor_cell_index.cmp(&focus_cell_index) {
Ordering::Less => anchor_offset,
Ordering::Equal => std::cmp::min(anchor_offset, focus_offset),
Ordering::Greater => focus_offset,
}
}
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct ThreadAddedMessage {
#[builder(setter(into))]
pub notebook_id: String,
pub thread: Thread,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct ThreadItemAddedMessage {
#[builder(setter(into))]
pub notebook_id: String,
#[builder(setter(into))]
pub thread_id: String,
pub thread_item: ThreadItem,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct ThreadItemUpdatedMessage {
#[builder(setter(into))]
pub notebook_id: String,
#[builder(setter(into))]
pub thread_id: String,
pub thread_item: ThreadItem,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct ThreadDeletedMessage {
#[builder(setter(into))]
pub notebook_id: String,
#[builder(setter(into))]
pub thread_id: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
#[cfg_attr(
feature = "fp-bindgen",
derive(Serializable),
fp(rust_module = "fiberplane_models::realtime")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct UserTypingCommentServerMessage {
#[builder(setter(into))]
pub notebook_id: String,
#[builder(setter(into))]
pub thread_id: String,
pub user: UserSummary,
#[builder(setter(into))]
pub updated_at: Timestamp,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize_reject_reason() {
let reason = OutdatedRejectReason {
current_revision: 1,
};
let reason = RejectReason::Outdated(reason);
let result = serde_json::to_string(&reason);
if let Err(err) = result {
panic!("Unexpected error occurred: {err:?}");
}
}
}