Skip to main content

fiberplane_models/notebooks/
operations.rs

1use super::{TableColumnDefinition, TableColumnId, TableRow, TableRowValue};
2use crate::data_sources::SelectedDataSource;
3use crate::formatting::Formatting;
4use crate::front_matter_schemas::{FrontMatterSchemaEntry, FrontMatterValueSchema};
5use crate::notebooks::{
6    front_matter::{FrontMatter, FrontMatterValue},
7    Cell, Label,
8};
9use crate::timestamps::TimeRange;
10#[cfg(feature = "fp-bindgen")]
11use fp_bindgen::prelude::*;
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use typed_builder::TypedBuilder;
15
16/// An operation is the representation for a mutation to be performed to a notebook.
17///
18/// Operations are intended to be atomic (they should either be performed in their entirety or not
19/// at all), while also capturing the intent of the user.
20///
21/// For more information, please see RFC 8:
22///   https://www.notion.so/fiberplane/RFC-8-Notebook-Operations-f9d18676d0d9437d81de30faa219deb4
23#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
24#[cfg_attr(
25    feature = "fp-bindgen",
26    derive(Serializable),
27    fp(rust_module = "fiberplane_models::notebooks::operations")
28)]
29#[non_exhaustive]
30#[serde(tag = "type", rename_all = "snake_case")]
31#[allow(clippy::large_enum_variant)]
32pub enum Operation {
33    // Cell-level operations.
34    MoveCells(MoveCellsOperation),
35    ReplaceCells(ReplaceCellsOperation),
36
37    // Text-level operation.
38    ReplaceText(ReplaceTextOperation),
39
40    // Time-range operation.
41    UpdateNotebookTimeRange(UpdateNotebookTimeRangeOperation),
42
43    /// **Deprecated:** Please use `ReplaceText` with `cell_id == TITLE_CELL_ID` instead.
44    UpdateNotebookTitle(UpdateNotebookTitleOperation),
45
46    // Data source selection.
47    SetSelectedDataSource(SetSelectedDataSourceOperation),
48
49    // Label operations.
50    AddLabel(AddLabelOperation),
51    ReplaceLabel(ReplaceLabelOperation),
52    RemoveLabel(RemoveLabelOperation),
53
54    // Front matter operations.
55    ClearFrontMatter(ClearFrontMatterOperation),
56    InsertFrontMatterSchema(InsertFrontMatterSchemaOperation),
57    UpdateFrontMatterSchema(UpdateFrontMatterSchemaOperation),
58    MoveFrontMatterSchema(MoveFrontMatterSchemaOperation),
59    RemoveFrontMatterSchema(RemoveFrontMatterSchemaOperation),
60    /// **Deprecated:** Full front matter updates should be avoided, granular update operations are
61    /// better for conflict handling.
62    UpdateFrontMatter(UpdateFrontMatterOperation),
63
64    // Table cell operations.
65    // TODO: We'll probably want Move operations for columns and rows later as well.
66    InsertTableColumn(InsertTableColumnOperation),
67    RemoveTableColumn(RemoveTableColumnOperation),
68    UpdateTableColumnDefinition(UpdateTableColumnDefinitionOperation),
69    InsertTableRow(InsertTableRowOperation),
70    RemoveTableRow(RemoveTableRowOperation),
71}
72
73/// Moves one or more cells.
74#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
75#[cfg_attr(
76    feature = "fp-bindgen",
77    derive(Serializable),
78    fp(rust_module = "fiberplane_models::notebooks::operations")
79)]
80#[non_exhaustive]
81#[serde(rename_all = "camelCase")]
82pub struct MoveCellsOperation {
83    /// IDs of all the cells to be moved.
84    ///
85    /// These must be adjacent and given in the order they appear in the notebook.
86    pub cell_ids: Vec<String>,
87
88    /// Index the cells will be moved from. This is the index of the first cell before the move.
89    pub from_index: u32,
90
91    /// Index the cells will be moved to. This is the index of the first cell after the move.
92    pub to_index: u32,
93}
94
95/// Replaces one or more cells at once.
96///
97/// Note: This operation is relatively coarse and can be (ab)used to perform
98/// `ReplaceText` operations as well. In order to preserve intent as much as
99/// possible, please use `ReplaceText` where possible.
100///
101/// Note: This operation may not be used to move cells, other than the necessary
102/// corrections in cell indices that account for newly inserted and removed
103/// cells. Attempts to move cells to other indices will cause validation to
104/// fail.
105#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, TypedBuilder)]
106#[cfg_attr(
107    feature = "fp-bindgen",
108    derive(Serializable),
109    fp(rust_module = "fiberplane_models::notebooks::operations")
110)]
111#[non_exhaustive]
112#[serde(rename_all = "camelCase")]
113pub struct ReplaceCellsOperation {
114    /// Vector of the new cells, including their new indices.
115    ///
116    /// Indices of the new cells must be ordered incrementally to form a single,
117    /// cohesive range of cells.
118    ///
119    /// Note that "new" does not imply "newly inserted". If a cell with the same
120    /// ID is part of the `old_cells` field, it will merely be updated. Only
121    /// cells in the `new_cells` field that are not part of the `old_cells` will
122    /// be newly inserted.
123    #[builder(default)]
124    #[serde(default, skip_serializing_if = "Vec::is_empty")]
125    pub new_cells: Vec<CellWithIndex>,
126
127    /// Vector of the old cells, including their old indices.
128    ///
129    /// Indices of the old cells must be ordered incrementally to form a single,
130    /// cohesive range of cells.
131    ///
132    /// Note that "old" does not imply "removed". If a cell with the same
133    /// ID is part of the `new_cells` field, it will merely be updated. Only
134    /// cells in the `old_cells` field that are not part of the `new_cells` will
135    /// be removed.
136    #[builder(default)]
137    #[serde(default, skip_serializing_if = "Vec::is_empty")]
138    pub old_cells: Vec<CellWithIndex>,
139
140    /// Offset at which to split the first of the old cells.
141    ///
142    /// In this context, splitting means that the text of the cell in the
143    /// notebook is split in two at the split offset. The first part is kept,
144    /// while the second part (which must match the cell's text in `old_cells`)
145    /// is replaced with the text given in the first of the `new_cells`.
146    ///
147    /// If `None`, no cell is split.
148    #[builder(default, setter(strip_option))]
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub split_offset: Option<u32>,
151
152    /// Offset from which to merge the remainder of the last old cell.
153    ///
154    /// In this context, merging means that the text of the new cell is merged
155    /// from two parts. The first part comes the last of the `new_cells`, while
156    /// the second part is what remains of the cell in the notebook after the
157    /// merge offset.
158    ///
159    /// If `None`, no cells are merged.
160    #[builder(default, setter(strip_option))]
161    #[serde(default, skip_serializing_if = "Option::is_none")]
162    pub merge_offset: Option<u32>,
163
164    /// Optional cells which are updated as a result of the replacing of other
165    /// cells. This is intended to be used for cells that reference the
166    /// `new_cells` and which now need to be updated as a result of the
167    /// operation being applied to those cells.
168    ///
169    /// These referencing cells may also be newly inserted if they are not
170    /// included in the `old_referencing_cells`.
171    ///
172    /// Indices of new referencing cells do not need to form a cohesive range,
173    /// but they should still be ordered in ascending order.
174    #[builder(default)]
175    #[serde(default, skip_serializing_if = "Vec::is_empty")]
176    pub new_referencing_cells: Vec<CellWithIndex>,
177
178    /// Optional cells which are updated as a result of the replacing of other
179    /// cells. This is intended to be used for cells that reference the
180    /// `old_cells` and which now need to be updated as a result of the
181    /// operation being applied to those cells.
182    ///
183    /// These referencing cells may also be removed if they are not included in
184    /// the `new_referencing_cells`.
185    ///
186    /// Indices of old referencing cells do not need to form a cohesive range,
187    /// but they should still be ordered in ascending order.
188    #[builder(default)]
189    #[serde(default, skip_serializing_if = "Vec::is_empty")]
190    pub old_referencing_cells: Vec<CellWithIndex>,
191}
192
193impl ReplaceCellsOperation {
194    /// Returns all the new cells, including the ones in the
195    /// `new_referencing_cells` field.
196    ///
197    /// Note that new cells doesn't imply newly inserted, since cells that are
198    /// merely updated are part of the new cells as well. See
199    /// `all_newly_inserted_cells()` if that's what you're looking for.
200    pub fn all_new_cells(&self) -> impl Iterator<Item = &CellWithIndex> {
201        self.old_cells
202            .iter()
203            .chain(self.old_referencing_cells.iter())
204    }
205
206    /// Returns all the newly inserted cells, including the ones in the
207    /// `new_referencing_cells` field.
208    pub fn all_newly_inserted_cells(&self) -> impl Iterator<Item = &CellWithIndex> {
209        self.newly_inserted_cells()
210            .chain(self.newly_inserted_referencing_cells())
211    }
212
213    /// Returns all the old cells, including the ones in the
214    /// `old_referencing_cells` field.
215    ///
216    /// Note that old cells doesn't imply removed cells, since cells that are
217    /// merely updated are part of the old cells as well. See
218    /// `all_old_removed_cells()` if that's what you're looking for.
219    pub fn all_old_cells(&self) -> impl Iterator<Item = &CellWithIndex> {
220        self.old_cells
221            .iter()
222            .chain(self.old_referencing_cells.iter())
223    }
224
225    /// Returns all the old removed cells, including the ones from the
226    /// `old_referencing_cells` field.
227    pub fn all_old_removed_cells(&self) -> impl Iterator<Item = &CellWithIndex> {
228        self.old_removed_cells()
229            .chain(self.old_removed_referencing_cells())
230    }
231
232    /// Returns all newly inserted cells, excluding referencing cells.
233    pub fn newly_inserted_cells(&self) -> impl Iterator<Item = &CellWithIndex> {
234        self.new_cells.iter().filter(move |new_cell| {
235            !self
236                .old_cells
237                .iter()
238                .any(|old_cell| old_cell.id() == new_cell.id())
239        })
240    }
241
242    /// Returns all newly inserted referencing cells.
243    pub fn newly_inserted_referencing_cells(&self) -> impl Iterator<Item = &CellWithIndex> {
244        self.new_referencing_cells.iter().filter(move |new_cell| {
245            !self
246                .old_referencing_cells
247                .iter()
248                .any(|old_cell| old_cell.id() == new_cell.id())
249        })
250    }
251
252    /// Returns all old cells that will be removed, excluding referencing cells.
253    pub fn old_removed_cells(&self) -> impl Iterator<Item = &CellWithIndex> {
254        self.old_cells.iter().filter(move |old_cell| {
255            !self
256                .new_cells
257                .iter()
258                .any(|new_cell| new_cell.id() == old_cell.id())
259        })
260    }
261
262    /// Returns all old referencing cells that will be removed.
263    pub fn old_removed_referencing_cells(&self) -> impl Iterator<Item = &CellWithIndex> {
264        self.old_referencing_cells.iter().filter(move |old_cell| {
265            !self
266                .new_referencing_cells
267                .iter()
268                .any(|new_cell| new_cell.id() == old_cell.id())
269        })
270    }
271}
272
273/// Replaces the part of the content in any content type cell or the title of a graph cell.
274#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
275#[cfg_attr(
276    feature = "fp-bindgen",
277    derive(Serializable),
278    fp(rust_module = "fiberplane_models::notebooks::operations")
279)]
280#[non_exhaustive]
281#[serde(rename_all = "camelCase")]
282pub struct ReplaceTextOperation {
283    /// ID of the cell whose text we're modifying.
284    #[builder(setter(into))]
285    pub cell_id: String,
286
287    /// Field to update the text of.
288    #[builder(default, setter(into, strip_option))]
289    #[serde(default, skip_serializing_if = "Option::is_none")]
290    pub field: Option<String>,
291
292    /// Starting offset where we will be replacing the text.
293    ///
294    /// Please be aware this offset refers to the position of a Unicode Scalar Value (non-surrogate
295    /// codepoint) in the cell text, which may require additional effort to determine correctly.
296    pub offset: u32,
297
298    /// The new text value we're inserting.
299    #[builder(default, setter(into))]
300    pub new_text: String,
301
302    /// Optional formatting that we wish to apply to the new text.
303    ///
304    /// Offsets in the formatting are relative to the start of the new text.
305    #[builder(default, setter(strip_option))]
306    #[serde(default, skip_serializing_if = "Option::is_none")]
307    pub new_formatting: Option<Formatting>,
308
309    /// The old text that we're replacing.
310    #[builder(default, setter(into))]
311    pub old_text: String,
312
313    /// Optional formatting that was applied to the old text. This should be **all** the formatting
314    /// annotations that were *inside* the `old_text` before this operation was applied. However,
315    /// it is at the operation's discretion whether or not to include annotations that are at the
316    /// old text's boundaries.
317    ///
318    /// Offsets in the formatting are relative to the start of the old text.
319    #[builder(default, setter(strip_option))]
320    #[serde(default, skip_serializing_if = "Option::is_none")]
321    pub old_formatting: Option<Formatting>,
322}
323
324/// Updates the notebook time range.
325#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
326#[cfg_attr(
327    feature = "fp-bindgen",
328    derive(Serializable),
329    fp(rust_module = "fiberplane_models::notebooks::operations")
330)]
331#[non_exhaustive]
332#[serde(rename_all = "camelCase")]
333pub struct UpdateNotebookTimeRangeOperation {
334    pub old_time_range: TimeRange,
335    pub time_range: TimeRange,
336}
337
338/// Updates the notebook title.
339#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
340#[cfg_attr(
341    feature = "fp-bindgen",
342    derive(Serializable),
343    fp(rust_module = "fiberplane_models::notebooks::operations")
344)]
345#[non_exhaustive]
346#[serde(rename_all = "camelCase")]
347pub struct UpdateNotebookTitleOperation {
348    #[builder(default, setter(into))]
349    pub old_title: String,
350
351    #[builder(default, setter(into))]
352    pub title: String,
353}
354
355#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
356#[cfg_attr(
357    feature = "fp-bindgen",
358    derive(Serializable),
359    fp(rust_module = "fiberplane_models::notebooks::operations")
360)]
361#[non_exhaustive]
362#[serde(rename_all = "camelCase")]
363pub struct SetSelectedDataSourceOperation {
364    #[builder(setter(into))]
365    pub provider_type: String,
366
367    #[builder(default)]
368    #[serde(default, skip_serializing_if = "Option::is_none")]
369    pub old_selected_data_source: Option<SelectedDataSource>,
370
371    #[builder(default)]
372    #[serde(default, skip_serializing_if = "Option::is_none")]
373    pub new_selected_data_source: Option<SelectedDataSource>,
374}
375
376#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TypedBuilder)]
377#[cfg_attr(
378    feature = "fp-bindgen",
379    derive(Serializable),
380    fp(rust_module = "fiberplane_models::notebooks::operations")
381)]
382#[non_exhaustive]
383#[serde(rename_all = "camelCase")]
384pub struct CellWithIndex {
385    pub cell: Cell,
386    pub index: u32,
387}
388
389impl CellWithIndex {
390    pub fn new(cell: Cell, index: u32) -> CellWithIndex {
391        CellWithIndex { cell, index }
392    }
393
394    pub fn formatting(&self) -> Option<&Formatting> {
395        self.cell.formatting()
396    }
397
398    pub fn id(&self) -> &str {
399        self.cell.id()
400    }
401
402    pub fn text(&self) -> Option<&str> {
403        self.cell.text()
404    }
405}
406
407/// Add an label to an notebook.
408#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
409#[cfg_attr(
410    feature = "fp-bindgen",
411    derive(Serializable),
412    fp(rust_module = "fiberplane_models::notebooks::operations")
413)]
414#[non_exhaustive]
415#[serde(rename_all = "camelCase")]
416pub struct AddLabelOperation {
417    /// The new label
418    pub label: Label,
419}
420
421/// Replace an label in an notebook.
422#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
423#[cfg_attr(
424    feature = "fp-bindgen",
425    derive(Serializable),
426    fp(rust_module = "fiberplane_models::notebooks::operations")
427)]
428#[non_exhaustive]
429#[serde(rename_all = "camelCase")]
430pub struct ReplaceLabelOperation {
431    // The previous label
432    pub old_label: Label,
433
434    // The new label
435    pub new_label: Label,
436}
437
438/// Remove an label in an notebook.
439#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
440#[cfg_attr(
441    feature = "fp-bindgen",
442    derive(Serializable),
443    fp(rust_module = "fiberplane_models::notebooks::operations")
444)]
445#[non_exhaustive]
446#[serde(rename_all = "camelCase")]
447pub struct RemoveLabelOperation {
448    pub label: Label,
449}
450
451/// Replaces front matter in a notebook
452#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
453#[cfg_attr(
454    feature = "fp-bindgen",
455    derive(Serializable),
456    fp(rust_module = "fiberplane_models::notebooks::operations")
457)]
458#[non_exhaustive]
459#[serde(rename_all = "camelCase")]
460pub struct UpdateFrontMatterOperation {
461    pub old_front_matter: FrontMatter,
462    pub new_front_matter: FrontMatter,
463}
464
465/// Removes front matter in a notebook
466#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
467#[cfg_attr(
468    feature = "fp-bindgen",
469    derive(Serializable),
470    fp(rust_module = "fiberplane_models::notebooks::operations")
471)]
472#[non_exhaustive]
473#[serde(rename_all = "camelCase")]
474pub struct ClearFrontMatterOperation {
475    pub front_matter: FrontMatter,
476}
477
478/// Adds front matter entries in a notebook
479#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TypedBuilder)]
480#[cfg_attr(
481    feature = "fp-bindgen",
482    derive(Serializable),
483    fp(rust_module = "fiberplane_models::notebooks::operations")
484)]
485#[non_exhaustive]
486#[serde(rename_all = "camelCase")]
487pub struct InsertFrontMatterSchemaOperation {
488    // NOTE: No strip_option here because the strongly typed builder makes
489    // it hard to revert operations in fiberplane-ot otherwise
490    /// The Front Matter Schema key that is just before the insertion point. This
491    /// is solely used for consistency checks when validating the operation.
492    #[builder(default, setter(into))]
493    #[serde(default, skip_serializing_if = "Option::is_none")]
494    pub key_of_entry_before_insertion_location: Option<String>,
495
496    /// The Front Matter Schema key that is just after the insertion point. This
497    /// is solely used for consistency checks when validating the operation.
498    #[builder(default, setter(into))]
499    #[serde(default, skip_serializing_if = "Option::is_none")]
500    pub key_of_entry_after_insertion_location: Option<String>,
501
502    /// The index to insert the new front matter schema into
503    pub to_index: u32,
504
505    /// The new entries to add to the front matter schema, with their new values
506    pub insertions: Vec<FrontMatterSchemaRow>,
507}
508
509#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
510#[cfg_attr(
511    feature = "fp-bindgen",
512    derive(Serializable),
513    fp(rust_module = "fiberplane_models::notebooks::operations")
514)]
515#[serde(rename_all = "camelCase")]
516#[non_exhaustive]
517pub struct FrontMatterSchemaRow {
518    #[builder(setter(into))]
519    pub key: String,
520
521    #[builder(setter(into))]
522    pub schema: FrontMatterValueSchema,
523
524    #[serde(default, skip_serializing_if = "Option::is_none")]
525    #[builder(default, setter(into))]
526    pub value: Option<FrontMatterValue>,
527}
528
529impl From<(FrontMatterSchemaEntry, Option<Value>)> for FrontMatterSchemaRow {
530    fn from((schema, value): (FrontMatterSchemaEntry, Option<Value>)) -> Self {
531        Self {
532            key: schema.key,
533            schema: schema.schema,
534            value: value.map(Into::into),
535        }
536    }
537}
538
539/// Changes the expected schema of a front matter key in a notebook and/or the
540/// value attached to a schema
541#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TypedBuilder)]
542#[cfg_attr(
543    feature = "fp-bindgen",
544    derive(Serializable),
545    fp(rust_module = "fiberplane_models::notebooks::operations")
546)]
547#[non_exhaustive]
548#[serde(rename_all = "camelCase")]
549pub struct UpdateFrontMatterSchemaOperation {
550    /// The key of the front matter schema to update.
551    #[builder(setter(into))]
552    pub key: String,
553
554    /// The previous schema used for that front matter key. The old value is used
555    /// to make consistency checks, as well as revert the operation.
556    #[builder(setter(into))]
557    pub old_schema: FrontMatterValueSchema,
558
559    // NOTE: No strip_option here because the strongly typed builder makes
560    // it hard to revert operations in fiberplane-ot otherwise
561    /// The previous value for that front matter key. It is used for consistency checks,
562    /// as well as making reverting operations possible.
563    #[builder(default)]
564    #[serde(default, skip_serializing_if = "Option::is_none")]
565    pub old_value: Option<FrontMatterValue>,
566
567    // NOTE: No strip_option here because the strongly typed builder makes
568    // it hard to revert operations in fiberplane-ot otherwise
569    /// The new schema to use, if unspecified the operation will leave the schema
570    /// untouched (so the operation is only being used to edit the associated value).
571    ///
572    /// If a new schema is specified, and the data type does _not_ match between the
573    /// old and the new one, then the old value will be wiped anyway.
574    #[builder(setter(into))]
575    #[serde(default, skip_serializing_if = "Option::is_none")]
576    pub new_schema: Option<FrontMatterValueSchema>,
577
578    /// The new value to set for the front matter entry.
579    ///
580    /// If this attribute is `None` or `null` it can mean multiple things depending on
581    /// the other attributes:
582    /// - if `delete_value` is `false`, this means we want to keep the `old_value`
583    ///   + it is impossible to keep the `old_value` if the schemas are incompatible. In that
584    ///     case we use the `default_value` of the new schema (or nothing if there’s no default)
585    /// - if `delete_value` is `true`, this means we want to wipe the value from the front
586    ///   matter in all cases.
587    #[builder(default)]
588    #[serde(default, skip_serializing_if = "Option::is_none")]
589    pub new_value: Option<FrontMatterValue>,
590
591    /// Switch that controls front matter value edition alongside `new_value`, when
592    /// `new_value` is None.
593    #[builder(setter(strip_bool))]
594    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
595    pub delete_value: bool,
596}
597
598/// Moves front matter entries in a notebook
599#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
600#[cfg_attr(
601    feature = "fp-bindgen",
602    derive(Serializable),
603    fp(rust_module = "fiberplane_models::notebooks::operations")
604)]
605#[non_exhaustive]
606#[serde(rename_all = "camelCase")]
607pub struct MoveFrontMatterSchemaOperation {
608    /// The keys that will be moved in the front matter. They should be a range of
609    /// consecutive front matter entries, matching the existing front matter schema
610    /// at the index pointed to by `from_index`
611    pub keys: Vec<String>,
612
613    /// Index the key will be moved from. This is the index of the first front matter key before the move.
614    pub from_index: u32,
615
616    /// Index the key will be moved to. This is the index of the first front matter key after the move.
617    pub to_index: u32,
618}
619
620/// Removes front matter entries in a notebook
621#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
622#[cfg_attr(
623    feature = "fp-bindgen",
624    derive(Serializable),
625    fp(rust_module = "fiberplane_models::notebooks::operations")
626)]
627#[non_exhaustive]
628#[serde(rename_all = "camelCase")]
629pub struct RemoveFrontMatterSchemaOperation {
630    /// The key of the front matter schema element lying just before the deletion range, i.e. _not_ removed.
631    /// This is used to make consistency checks when validating operation.
632    #[builder(default, setter(into))]
633    #[serde(default, skip_serializing_if = "Option::is_none")]
634    pub key_of_entry_before_deletion_range: Option<String>,
635
636    /// The key of the front matter schema element lying just after the deletion range, i.e. _not_ removed.
637    /// This is used to make consistency checks when validating operation.
638    #[builder(default, setter(into))]
639    #[serde(default, skip_serializing_if = "Option::is_none")]
640    pub key_of_entry_after_deletion_range: Option<String>,
641
642    /// The index to start removing elements from. This is the index of the first element that will be
643    /// deleted, and should match the first element of the `deletions` array.
644    pub from_index: u32,
645
646    /// Elements that should be deleted, with their last known values
647    pub deletions: Vec<FrontMatterSchemaRow>,
648}
649
650#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
651#[cfg_attr(
652    feature = "fp-bindgen",
653    derive(Serializable),
654    fp(rust_module = "fiberplane_models::notebooks::operations")
655)]
656#[non_exhaustive]
657#[serde(rename_all = "camelCase")]
658pub struct CellAppendText {
659    #[builder(setter(into))]
660    pub content: String,
661
662    #[builder(default)]
663    #[serde(default)]
664    pub formatting: Formatting,
665}
666
667#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
668#[cfg_attr(
669    feature = "fp-bindgen",
670    derive(Serializable),
671    fp(rust_module = "fiberplane_models::notebooks::operations")
672)]
673#[non_exhaustive]
674#[serde(rename_all = "camelCase")]
675pub struct CellReplaceText {
676    /// Starting offset where we will be replacing the text.
677    ///
678    /// Please be aware this offset refers to the position of a Unicode Scalar Value (non-surrogate
679    /// codepoint) in the cell text, which may require additional effort to determine correctly.
680    pub offset: u32,
681
682    /// The new text value we're inserting.
683    #[builder(default, setter(into))]
684    pub new_text: String,
685
686    /// Optional formatting that we wish to apply to the new text.
687    ///
688    /// Offsets in the formatting are relative to the start of the new text.
689    #[builder(default, setter(strip_option))]
690    #[serde(default, skip_serializing_if = "Option::is_none")]
691    pub new_formatting: Option<Formatting>,
692
693    /// The old text that we're replacing.
694    #[builder(default, setter(into))]
695    pub old_text: String,
696
697    /// Optional formatting that was applied to the old text. This should be **all** the formatting
698    /// annotations that were *inside* the `old_text` before this operation was applied. However,
699    /// it is at the operation's discretion whether or not to include annotations that are at the
700    /// old text's boundaries.
701    ///
702    /// Offsets in the formatting are relative to the start of the old text.
703    #[builder(default, setter(strip_option))]
704    #[serde(default, skip_serializing_if = "Option::is_none")]
705    pub old_formatting: Option<Formatting>,
706}
707
708#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
709#[cfg_attr(
710    feature = "fp-bindgen",
711    derive(Serializable),
712    fp(rust_module = "fiberplane_models::notebooks::operations")
713)]
714#[non_exhaustive]
715#[serde(rename_all = "camelCase")]
716pub struct InsertTableColumnOperation {
717    /// ID of the table cell.
718    #[builder(setter(into))]
719    pub cell_id: String,
720
721    /// Definition for the column.
722    pub column_def: TableColumnDefinition,
723
724    /// The index at which to insert the column.
725    pub index: u32,
726
727    /// The values to insert in the column.
728    ///
729    /// The amount of values should match the amount of rows in the table.
730    #[builder(setter(into))]
731    pub values: Vec<TableRowValue>,
732}
733
734#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
735#[cfg_attr(
736    feature = "fp-bindgen",
737    derive(Serializable),
738    fp(rust_module = "fiberplane_models::notebooks::operations")
739)]
740#[non_exhaustive]
741#[serde(rename_all = "camelCase")]
742pub struct RemoveTableColumnOperation {
743    /// ID of the table cell.
744    #[builder(setter(into))]
745    pub cell_id: String,
746
747    /// Definition of the column being removed.
748    pub column_def: TableColumnDefinition,
749
750    /// The index of the column being removed.
751    pub index: u32,
752
753    /// The values that are being removed together with the column.
754    ///
755    /// The amount of values should match the amount of rows in the table.
756    #[builder(setter(into))]
757    pub values: Vec<TableRowValue>,
758}
759
760#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
761#[cfg_attr(
762    feature = "fp-bindgen",
763    derive(Serializable),
764    fp(rust_module = "fiberplane_models::notebooks::operations")
765)]
766#[non_exhaustive]
767#[serde(rename_all = "camelCase")]
768pub struct UpdateTableColumnDefinitionOperation {
769    /// ID of the table cell.
770    #[builder(setter(into))]
771    pub cell_id: String,
772
773    /// ID of the column being updated.
774    pub column_id: TableColumnId,
775
776    /// New heading text.
777    #[builder(setter(into))]
778    pub new_title: String,
779
780    /// Old heading text.
781    #[builder(setter(into))]
782    pub old_title: String,
783}
784
785#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
786#[cfg_attr(
787    feature = "fp-bindgen",
788    derive(Serializable),
789    fp(rust_module = "fiberplane_models::notebooks::operations")
790)]
791#[non_exhaustive]
792#[serde(rename_all = "camelCase")]
793pub struct InsertTableRowOperation {
794    /// ID of the table cell.
795    #[builder(setter(into))]
796    pub cell_id: String,
797
798    /// The row being inserted.
799    pub row: TableRow,
800
801    /// The index at which to insert the row.
802    pub index: u32,
803}
804
805#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
806#[cfg_attr(
807    feature = "fp-bindgen",
808    derive(Serializable),
809    fp(rust_module = "fiberplane_models::notebooks::operations")
810)]
811#[non_exhaustive]
812#[serde(rename_all = "camelCase")]
813pub struct RemoveTableRowOperation {
814    /// ID of the table cell.
815    #[builder(setter(into))]
816    pub cell_id: String,
817
818    /// The row being removed.
819    pub row: TableRow,
820
821    /// The index of the row being removed.
822    pub index: u32,
823}