Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions src/lossless.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ impl LosslessEffectiveMappingEntry {
#[derive(Clone, Debug, PartialEq)]
pub struct LosslessStream {
source: Arc<str>,
options: LoadOptions,
documents: Vec<LosslessDocument>,
nodes: Vec<LosslessNode>,
anchors: Vec<LosslessAnchor>,
Expand All @@ -352,7 +353,7 @@ impl LosslessStream {
let events = options.stream_events(input)?.collect::<Result<Vec<_>>>()?;
let source: Arc<str> = Arc::from(input);
let trivia = scan_trivia(&source);
Builder::new(source, events, trivia).build()
Builder::new(source, events, trivia, options).build()
}

/// Returns the original YAML source.
Expand Down Expand Up @@ -1427,7 +1428,7 @@ impl LosslessEdit<'_> {
));
}
let replacement = replacement.into();
ensure_scalar_fragment(&replacement, current.span())?;
ensure_scalar_fragment_with_options(&replacement, current.span(), self.stream.options)?;
self.replace_node_source(node, replacement)
}

Expand Down Expand Up @@ -1512,7 +1513,7 @@ impl LosslessEdit<'_> {
self.insert_block_mapping_entry_source_with_options(
mapping,
entry_source,
LoadOptions::new(),
self.stream.options,
)
}

Expand Down Expand Up @@ -1621,7 +1622,7 @@ impl LosslessEdit<'_> {
self.insert_flow_mapping_entry_source_with_options(
mapping,
entry_source,
LoadOptions::new(),
self.stream.options,
)
}

Expand Down Expand Up @@ -1723,7 +1724,7 @@ impl LosslessEdit<'_> {
sequence,
index,
replacement,
LoadOptions::new(),
self.stream.options,
)
}

Expand Down Expand Up @@ -1780,7 +1781,7 @@ impl LosslessEdit<'_> {
sequence,
index,
item_source,
LoadOptions::new(),
self.stream.options,
)
}

Expand Down Expand Up @@ -1904,7 +1905,7 @@ impl LosslessEdit<'_> {
sequence,
index,
item_source,
LoadOptions::new(),
self.stream.options,
)
}

Expand Down Expand Up @@ -2012,7 +2013,8 @@ impl LosslessEdit<'_> {

/// Returns validated edited YAML with untouched source bytes preserved.
pub fn finish(self) -> Result<String> {
self.finish_with_options(LoadOptions::new())
let options = self.stream.options;
self.finish_with_options(options)
}

fn finish_with_options(mut self, options: LoadOptions) -> Result<String> {
Expand Down Expand Up @@ -2580,6 +2582,7 @@ impl LosslessTrivia {

struct Builder {
source: Arc<str>,
options: LoadOptions,
events: Vec<Event>,
documents: Vec<LosslessDocument>,
nodes: Vec<LosslessNode>,
Expand All @@ -2592,9 +2595,15 @@ struct Builder {
}

impl Builder {
fn new(source: Arc<str>, events: Vec<Event>, trivia: Vec<LosslessTrivia>) -> Self {
fn new(
source: Arc<str>,
events: Vec<Event>,
trivia: Vec<LosslessTrivia>,
options: LoadOptions,
) -> Self {
Self {
source,
options,
events,
documents: Vec::new(),
nodes: Vec::new(),
Expand Down Expand Up @@ -2673,6 +2682,7 @@ impl Builder {
}
Ok(LosslessStream {
source: self.source,
options: self.options,
documents: self.documents,
nodes: self.nodes,
anchors: self.anchors,
Expand Down Expand Up @@ -3566,10 +3576,6 @@ fn scan_trivia(input: &Arc<str>) -> Vec<LosslessTrivia> {
trivia
}

fn ensure_scalar_fragment(replacement: &str, span: Span) -> Result<()> {
ensure_scalar_fragment_with_options(replacement, span, LoadOptions::new())
}

fn ensure_scalar_fragment_with_options(
replacement: &str,
span: Span,
Expand Down
73 changes: 73 additions & 0 deletions tests/lossless_roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,79 @@ fn lossless_edit_rejects_overlapping_replacements() {
assert!(error.to_string().contains("lossless replacements overlap"));
}

#[test]
fn lossless_edit_preserves_custom_load_options_for_noop_and_mutating_edits() {
let mut input = String::from("items:\n");
for index in 0..=DEFAULT_MAX_COLLECTION_ITEMS {
input.push_str(" - item");
input.push_str(&index.to_string());
input.push('\n');
}
input.push_str("flag: on\n");

let options = LoadOptions::yaml_1_1().without_collection_limit();
let stream = saneyaml::parse_lossless_with_options(&input, options)
.expect("custom options allow the wide collection");
let flag = stream
.resolve_path(0, &[PathSegment::from("flag")])
.expect("flag path");
assert!(matches!(
stream.node(flag).expect("flag node").kind(),
LosslessNodeKind::Scalar { value, .. } if value == "true"
));

assert_eq!(stream.edit().finish().expect("no-op edit"), input);

let mut edit = stream.edit();
edit.replace_scalar_source(flag, "off")
.expect("replace YAML 1.1 boolean scalar");
let output = edit.finish().expect("custom options validate the edit");
assert_eq!(output, input.replacen("flag: on", "flag: off", 1));
}

#[test]
fn lossless_structural_edit_enforces_custom_fragment_limits() {
let stream = saneyaml::parse_lossless_with_options(
"items:\n - one\n",
LoadOptions::new().max_scalar_bytes(5),
)
.expect("source is within the custom scalar limit");
let sequence = stream
.resolve_path(0, &[PathSegment::from("items")])
.expect("items path");

let error = stream
.edit()
.insert_block_sequence_item_source(sequence, 1, "longer")
.expect_err("structural insertion must use the stream's scalar limit");
assert!(error.to_string().contains("scalar"));
}

#[test]
fn lossless_scalar_edit_enforces_custom_fragment_limits() {
let stream = saneyaml::parse_lossless_with_options(
"name: old\n",
LoadOptions::new().max_scalar_bytes(5),
)
.expect("source is within the custom scalar limit");
let scalar = stream
.nodes()
.iter()
.find(|node| {
matches!(
node.kind(),
LosslessNodeKind::Scalar { value, .. } if value == "old"
)
})
.expect("old scalar");

let error = stream
.edit()
.replace_scalar_source(scalar.id(), "longer")
.expect_err("scalar replacement must use the stream's scalar limit");
assert!(error.to_string().contains("scalar"));
}

fn flow_mapping_with_key<'a>(
stream: &'a saneyaml::LosslessStream,
key: &str,
Expand Down
Loading