weavatrix_edit/application/
batch.rs1use crate::{
2 error::{EditError, ErrorCode},
3 limits::BatchLimits,
4 model::ByteEdit,
5};
6
7use super::{
8 PreparedEdit, PreparedEdits,
9 batch_support::{
10 Occupancy, Totals, add_totals, admission_order, check_output, find_overlap, into_prepared,
11 merge_sorted, stage_edit, validate_before,
12 },
13 prepare::validate_byte_edit,
14 ranges::{compare_prepared, sort_prepared},
15};
16
17#[derive(Debug)]
23pub struct ByteEditBatch<'source> {
24 source: &'source str,
25 edits: Vec<PreparedEdit>,
26 occupied: Occupancy,
27 totals: Totals,
28 output_size: usize,
29 limits: BatchLimits,
30}
31
32impl<'source> ByteEditBatch<'source> {
33 pub fn new(source: &'source str) -> Result<Self, EditError> {
35 Self::with_limits(source, BatchLimits::default())
36 }
37
38 pub fn with_limits(source: &'source str, limits: BatchLimits) -> Result<Self, EditError> {
40 if source.len() > limits.max_source_bytes {
41 return Err(EditError::new(
42 ErrorCode::PlanTooLarge,
43 "source exceeds the batch source limit",
44 ));
45 }
46 Ok(Self {
47 source,
48 edits: Vec::new(),
49 occupied: Occupancy::new(),
50 totals: Totals::empty(),
51 output_size: source.len(),
52 limits,
53 })
54 }
55
56 pub fn push(&mut self, edit: ByteEdit) -> Result<(), EditError> {
58 let order = self.edits.len();
59 self.ensure_count(order)?;
60 validate_byte_edit(self.source, &edit, order)?;
61 validate_before(self.source, &edit, order)?;
62 let totals = add_totals(self.totals, &edit, self.limits, order)?;
63 self.ensure_no_overlap(&edit, order, None)?;
64 let output_size = check_output(
65 self.source.len(),
66 totals,
67 self.limits.max_output_bytes,
68 Some(order),
69 )?;
70 self.commit_one(edit, order, totals, output_size);
71 Ok(())
72 }
73
74 pub fn push_batch(&mut self, edits: Vec<ByteEdit>) -> Result<(), EditError> {
76 let base = self.edits.len();
77 self.ensure_batch_count(edits.len())?;
78 for (offset, edit) in edits.iter().enumerate() {
79 let order = admission_order(base, offset)?;
80 validate_byte_edit(self.source, edit, order)?;
81 }
82 let mut totals = self.totals;
83 for (offset, edit) in edits.iter().enumerate() {
84 let order = admission_order(base, offset)?;
85 validate_before(self.source, edit, order)?;
86 }
87 for (offset, edit) in edits.iter().enumerate() {
88 let order = admission_order(base, offset)?;
89 totals = add_totals(totals, edit, self.limits, order)?;
90 }
91 let mut staged = Vec::with_capacity(edits.len());
92 let mut staged_occupancy = Occupancy::new();
93 for (offset, edit) in edits.into_iter().enumerate() {
94 let order = admission_order(base, offset)?;
95 self.ensure_no_overlap(&edit, order, Some(&staged_occupancy))?;
96 stage_edit(edit, order, &mut staged, &mut staged_occupancy);
97 }
98 let output_size = check_output(
99 self.source.len(),
100 totals,
101 self.limits.max_output_bytes,
102 base.checked_add(staged.len())
103 .and_then(|value| value.checked_sub(1)),
104 )?;
105 sort_prepared(&mut staged);
106 self.edits = merge_sorted(core::mem::take(&mut self.edits), staged);
107 self.occupied.extend(staged_occupancy);
108 self.totals = totals;
109 self.output_size = output_size;
110 Ok(())
111 }
112
113 pub fn finish(self) -> Result<PreparedEdits<'source>, EditError> {
115 check_output(
116 self.source.len(),
117 self.totals,
118 self.limits.max_output_bytes,
119 None,
120 )?;
121 Ok(PreparedEdits::from_validated_parts(
122 self.source,
123 self.edits,
124 self.output_size,
125 self.limits.max_edits,
126 self.limits.max_output_bytes,
127 ))
128 }
129
130 #[must_use]
131 pub fn len(&self) -> usize {
132 self.edits.len()
133 }
134
135 #[must_use]
136 pub fn is_empty(&self) -> bool {
137 self.edits.is_empty()
138 }
139
140 fn ensure_count(&self, order: usize) -> Result<(), EditError> {
141 if order >= self.limits.max_edits {
142 return Err(EditError::new(
143 ErrorCode::PlanTooLarge,
144 "edit count exceeds the batch limit",
145 )
146 .at_edit(order));
147 }
148 Ok(())
149 }
150
151 fn ensure_batch_count(&self, incoming: usize) -> Result<(), EditError> {
152 if incoming > self.limits.max_edits.saturating_sub(self.edits.len()) {
153 return Err(EditError::new(
154 ErrorCode::PlanTooLarge,
155 "edit count exceeds the batch limit",
156 )
157 .at_edit(self.limits.max_edits));
158 }
159 Ok(())
160 }
161
162 fn ensure_no_overlap(
163 &self,
164 edit: &ByteEdit,
165 order: usize,
166 staged: Option<&Occupancy>,
167 ) -> Result<(), EditError> {
168 if let Some(related) = find_overlap(&self.occupied, staged, edit.start, edit.end) {
169 return Err(EditError::new(
170 ErrorCode::OverlappingEdits,
171 format!("edits {related} and {order} overlap"),
172 )
173 .at_edit(order)
174 .with_related_edit(related));
175 }
176 Ok(())
177 }
178
179 fn commit_one(&mut self, edit: ByteEdit, order: usize, totals: Totals, output_size: usize) {
180 let prepared = into_prepared(edit, order, &mut self.occupied);
181 let position = self
182 .edits
183 .partition_point(|current| compare_prepared(current, &prepared).is_le());
184 self.edits.insert(position, prepared);
185 self.totals = totals;
186 self.output_size = output_size;
187 }
188}