systemprompt_provider_contracts/documents/
rich_op.rs1use std::num::NonZeroUsize;
17
18use schemars::JsonSchema;
19use serde::{Deserialize, Serialize};
20
21use super::edit_op::{EditOp, NodeTarget};
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
24pub struct MarkSpec {
25 pub mark: String,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 pub value: Option<String>,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
31pub struct SetMarks {
32 pub node: NodeTarget,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub expected_hash: Option<String>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub find: Option<String>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub nth: Option<NonZeroUsize>,
39 #[serde(default, skip_serializing_if = "Vec::is_empty")]
40 pub add: Vec<MarkSpec>,
41 #[serde(default, skip_serializing_if = "Vec::is_empty")]
42 pub remove: Vec<String>,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
47#[serde(tag = "type", rename_all = "snake_case")]
48pub enum TextBlockType {
49 Paragraph,
50 Heading {
51 level: u8,
52 },
53 ListItem {
54 #[serde(default)]
55 level: u8,
56 #[serde(default)]
57 ordered: bool,
58 },
59 Quote,
60 Code {
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 language: Option<String>,
63 },
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
67pub struct SetBlockType {
68 pub node: NodeTarget,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
70 pub expected_hash: Option<String>,
71 pub block: TextBlockType,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
75pub struct InsertTable {
76 pub after: NodeTarget,
77 pub rows: Vec<Vec<String>>,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
83pub struct TableInsert {
84 pub table: NodeTarget,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub expected_hash: Option<String>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub at: Option<usize>,
89 #[serde(default, skip_serializing_if = "Vec::is_empty")]
90 pub cells: Vec<String>,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
95pub struct TableDelete {
96 pub table: NodeTarget,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub expected_hash: Option<String>,
99 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub row: Option<usize>,
101 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub col: Option<usize>,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
106pub struct InsertImage {
107 pub after: NodeTarget,
108 pub blob: String,
109 #[serde(default, skip_serializing_if = "String::is_empty")]
110 pub alt: String,
111}
112
113#[derive(Debug)]
116pub struct RichTarget<'a> {
117 pub node: &'a mut NodeTarget,
118 pub expected_hash: Option<&'a mut Option<String>>,
119}
120
121impl EditOp {
122 #[must_use]
123 pub const fn is_rich(&self) -> bool {
124 matches!(
125 self,
126 Self::SetMarks(_)
127 | Self::SetBlockType(_)
128 | Self::InsertTable(_)
129 | Self::TableInsertRow(_)
130 | Self::TableInsertCol(_)
131 | Self::TableDelete(_)
132 | Self::InsertImage(_)
133 | Self::SetParagraph(_)
134 | Self::InsertBlock(_)
135 | Self::UpdateTable(_)
136 | Self::SetSection(_)
137 | Self::SetStyle(_)
138 | Self::ApplyTheme(_)
139 )
140 }
141
142 #[must_use]
143 pub const fn is_document_level(&self) -> bool {
144 matches!(
145 self,
146 Self::SetSection(_) | Self::SetStyle(_) | Self::ApplyTheme(_)
147 )
148 }
149
150 pub const fn rich_target(&mut self) -> Option<RichTarget<'_>> {
151 let (node, expected_hash) = match self {
152 Self::SetMarks(op) => (&mut op.node, Some(&mut op.expected_hash)),
153 Self::SetBlockType(op) => (&mut op.node, Some(&mut op.expected_hash)),
154 Self::TableInsertRow(op) | Self::TableInsertCol(op) => {
155 (&mut op.table, Some(&mut op.expected_hash))
156 },
157 Self::TableDelete(op) => (&mut op.table, Some(&mut op.expected_hash)),
158 Self::InsertTable(op) => (&mut op.after, None),
159 Self::InsertImage(op) => (&mut op.after, None),
160 Self::SetParagraph(op) => (&mut op.node, Some(&mut op.expected_hash)),
161 Self::UpdateTable(op) => (&mut op.table, Some(&mut op.expected_hash)),
162 Self::InsertBlock(op) => (&mut op.after, None),
163 _ => return None,
164 };
165 Some(RichTarget {
166 node,
167 expected_hash,
168 })
169 }
170
171 #[must_use]
172 pub const fn rich_guard(&self) -> Option<(&NodeTarget, Option<&String>)> {
173 Some(match self {
174 Self::SetMarks(op) => (&op.node, op.expected_hash.as_ref()),
175 Self::SetBlockType(op) => (&op.node, op.expected_hash.as_ref()),
176 Self::TableInsertRow(op) | Self::TableInsertCol(op) => {
177 (&op.table, op.expected_hash.as_ref())
178 },
179 Self::TableDelete(op) => (&op.table, op.expected_hash.as_ref()),
180 Self::InsertTable(op) => (&op.after, None),
181 Self::InsertImage(op) => (&op.after, None),
182 Self::SetParagraph(op) => (&op.node, op.expected_hash.as_ref()),
183 Self::UpdateTable(op) => (&op.table, op.expected_hash.as_ref()),
184 Self::InsertBlock(op) => (&op.after, None),
185 _ => return None,
186 })
187 }
188}