Skip to content

Commit f8f5412

Browse files
fix: replace naive colon finder with query_exact prefix trim
Use doc.query_exact(route) to locate the value's byte span, then compute the key prefix as source[start_byte..value_start].trim_end(). This eliminates string-searching for the separator colon, which corrupted quoted keys containing colons. Removes find_key_colon entirely. Closes #43
1 parent 49d2567 commit f8f5412

1 file changed

Lines changed: 38 additions & 40 deletions

File tree

src/document.rs

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -353,39 +353,47 @@ fn apply_complex_replace(
353353
let start_byte = feature.location.byte_span.0 - ws_len;
354354
let end_byte = feature.location.byte_span.1;
355355

356-
// Find the colon separating key from value
357-
let colon_pos = find_key_colon(content_with_ws);
356+
// Use query_exact to locate the value's byte span independently.
357+
// This avoids string-searching for the colon separator, which breaks
358+
// on quoted keys containing colons (e.g. "http://example.com": 8080).
359+
let value_feature = doc
360+
.query_exact(route)
361+
.map_err(|e| format!("Query failed: {e}"))?;
358362

359-
let key_part = match colon_pos {
360-
Some(pos) => {
361-
let key = &content_with_ws[..pos + 1]; // through the colon
362-
key.to_string()
363+
let key_part = match value_feature {
364+
Some(vf) => {
365+
let prefix = source[start_byte..vf.location.byte_span.0].trim_end();
366+
if prefix.is_empty() {
367+
// Bare value (e.g. sequence item) — no key prefix
368+
let serialized = serde_yaml::to_string(value)
369+
.map_err(|e| format!("Failed to serialize YAML: {e}"))?;
370+
let trimmed = serialized.trim_end_matches('\n');
371+
372+
let line_start = source[..feature.location.byte_span.0]
373+
.rfind('\n')
374+
.map(|nl| nl + 1)
375+
.unwrap_or(0);
376+
let base_indent = feature.location.byte_span.0 - line_start;
377+
let indent_str = " ".repeat(base_indent);
378+
379+
let indented = indent_block(trimmed, &indent_str);
380+
381+
let mut result = source.to_string();
382+
result.replace_range(
383+
feature.location.byte_span.0..feature.location.byte_span.1,
384+
&indented,
385+
);
386+
if !result.ends_with('\n') {
387+
result.push('\n');
388+
}
389+
return yamlpath::Document::new(result)
390+
.map_err(|e| format!("Failed to re-parse YAML: {e}"));
391+
}
392+
prefix.to_string()
363393
}
364394
None => {
365-
// No colon found — bare value (e.g. sequence item)
366-
let serialized = serde_yaml::to_string(value)
367-
.map_err(|e| format!("Failed to serialize YAML: {e}"))?;
368-
let trimmed = serialized.trim_end_matches('\n');
369-
370-
let line_start = source[..feature.location.byte_span.0]
371-
.rfind('\n')
372-
.map(|nl| nl + 1)
373-
.unwrap_or(0);
374-
let base_indent = feature.location.byte_span.0 - line_start;
375-
let indent_str = " ".repeat(base_indent);
376-
377-
let indented = indent_block(trimmed, &indent_str);
378-
379-
let mut result = source.to_string();
380-
result.replace_range(
381-
feature.location.byte_span.0..feature.location.byte_span.1,
382-
&indented,
383-
);
384-
if !result.ends_with('\n') {
385-
result.push('\n');
386-
}
387-
return yamlpath::Document::new(result)
388-
.map_err(|e| format!("Failed to re-parse YAML: {e}"));
395+
// Absent value (e.g. `key:\n`) — content is just key+colon
396+
content_with_ws.trim_end().to_string()
389397
}
390398
};
391399

@@ -424,16 +432,6 @@ fn apply_complex_replace(
424432
yamlpath::Document::new(result).map_err(|e| format!("Failed to re-parse YAML: {e}"))
425433
}
426434

427-
/// Find the first colon (key-value separator) in a YAML fragment.
428-
///
429-
/// Uses a naive `find(':')`, consistent with yamlpatch's own Replace
430-
/// implementation. This means colons inside quoted keys will be
431-
/// misidentified — a known yamlpatch limitation that will be fixed
432-
/// uniformly when yamlpatch addresses it.
433-
fn find_key_colon(content: &str) -> Option<usize> {
434-
content.find(':')
435-
}
436-
437435
fn indent_block(content: &str, indent: &str) -> String {
438436
let mut result = String::new();
439437
for (i, line) in content.lines().enumerate() {

0 commit comments

Comments
 (0)