Skip to content

Commit 0918c85

Browse files
committed
Add more tests to cover possible regressions from 0.4.0 to 0.5.0
1 parent 22c65db commit 0918c85

4 files changed

Lines changed: 128 additions & 3 deletions

File tree

src/ungron.rs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ impl<'a> PathParser<'a> {
125125
self.expect_char('[')?;
126126
let segment = match self.peek_char() {
127127
Some('"') => PathSegment::Property(self.parse_json_string()?),
128+
Some('\'') => PathSegment::Property(self.parse_quoted_string('\'')?),
128129
Some(ch) if ch.is_ascii_digit() => PathSegment::Index(self.parse_index()?),
129130
Some(ch) => {
130131
return Err(anyhow!(
@@ -158,6 +159,61 @@ impl<'a> PathParser<'a> {
158159
Err(anyhow!("Unclosed string path segment"))
159160
}
160161

162+
fn parse_quoted_string(&mut self, delimiter: char) -> Result<String> {
163+
self.bump_char();
164+
165+
let mut result = String::new();
166+
while let Some(ch) = self.peek_char() {
167+
self.bump_char();
168+
if ch == delimiter {
169+
return Ok(result);
170+
}
171+
if ch == '\\' {
172+
result.push(self.parse_escape_sequence()?);
173+
} else {
174+
result.push(ch);
175+
}
176+
}
177+
178+
Err(anyhow!("Unclosed string path segment"))
179+
}
180+
181+
fn parse_escape_sequence(&mut self) -> Result<char> {
182+
let Some(ch) = self.peek_char() else {
183+
return Err(anyhow!("Unclosed escape sequence"));
184+
};
185+
self.bump_char();
186+
187+
match ch {
188+
'"' => Ok('"'),
189+
'\'' => Ok('\''),
190+
'\\' => Ok('\\'),
191+
'/' => Ok('/'),
192+
'b' => Ok('\u{0008}'),
193+
'f' => Ok('\u{000c}'),
194+
'n' => Ok('\n'),
195+
'r' => Ok('\r'),
196+
't' => Ok('\t'),
197+
'u' => self.parse_unicode_escape(),
198+
other => Ok(other),
199+
}
200+
}
201+
202+
fn parse_unicode_escape(&mut self) -> Result<char> {
203+
let start = self.pos;
204+
for _ in 0..4 {
205+
match self.peek_char() {
206+
Some(ch) if ch.is_ascii_hexdigit() => self.bump_char(),
207+
Some(ch) => return Err(anyhow!("Invalid unicode escape character '{}'", ch)),
208+
None => return Err(anyhow!("Unclosed unicode escape sequence")),
209+
}
210+
}
211+
212+
let code = u32::from_str_radix(&self.input[start..self.pos], 16)
213+
.context("Failed to parse unicode escape")?;
214+
char::from_u32(code).ok_or_else(|| anyhow!("Invalid unicode escape code point"))
215+
}
216+
161217
fn parse_index(&mut self) -> Result<usize> {
162218
let start = self.pos;
163219
while matches!(self.peek_char(), Some(ch) if ch.is_ascii_digit()) {
@@ -220,7 +276,12 @@ fn parse_value(right: &str) -> Result<JsonValue> {
220276
return Err(anyhow!("Expected assignment to end with ';'"));
221277
};
222278

223-
serde_json::from_str(value_src.trim()).context("Failed to parse gron value as JSON")
279+
let value_src = value_src.trim();
280+
if value_src.starts_with('\'') {
281+
return parse_single_quoted_value(value_src);
282+
}
283+
284+
serde_json::from_str(value_src).context("Failed to parse gron value as JSON")
224285
}
225286

226287
fn strip_trailing_semicolon(right: &str) -> Option<&str> {
@@ -234,7 +295,7 @@ fn strip_trailing_semicolon(right: &str) -> Option<&str> {
234295
Some(_) if ch == '\\' => escaped = true,
235296
Some(delimiter) if ch == delimiter => string_delimiter = None,
236297
Some(_) => {}
237-
None if ch == '"' => string_delimiter = Some(ch),
298+
None if ch == '"' || ch == '\'' => string_delimiter = Some(ch),
238299
None if ch == ';' => semicolon = Some(idx),
239300
None if !ch.is_whitespace() && semicolon.is_some() => return None,
240301
None => {}
@@ -244,6 +305,15 @@ fn strip_trailing_semicolon(right: &str) -> Option<&str> {
244305
semicolon.map(|idx| &right[..idx])
245306
}
246307

308+
fn parse_single_quoted_value(value_src: &str) -> Result<JsonValue> {
309+
let mut parser = PathParser::new(value_src);
310+
let value = parser.parse_quoted_string('\'')?;
311+
if !parser.input[parser.pos..].trim().is_empty() {
312+
return Err(anyhow!("Unexpected characters after string value"));
313+
}
314+
Ok(JsonValue::String(value))
315+
}
316+
247317
fn set_value_at_path(root: &mut JsonValue, path: &[PathSegment], value: JsonValue) -> Result<()> {
248318
let mut cur = root;
249319

src/xml2json.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,23 @@ pub fn parse_xml(xml: &str) -> Result<Value, XmlParseError> {
7171
let mut reader = Reader::from_str(xml);
7272
let mut stack = Vec::new();
7373
let mut output = Value::Null;
74+
let mut root_seen = false;
7475

7576
loop {
7677
match reader.read_event() {
77-
Ok(Event::Start(event)) => stack.push(node_from_start(&reader, &event)?),
78+
Ok(Event::Start(event)) => {
79+
if root_seen && stack.is_empty() {
80+
return Err(XmlParseError("multiple root elements".to_string()));
81+
}
82+
stack.push(node_from_start(&reader, &event)?);
83+
}
7884
Ok(Event::Empty(event)) => {
85+
if root_seen && stack.is_empty() {
86+
return Err(XmlParseError("multiple root elements".to_string()));
87+
}
7988
let node = node_from_start(&reader, &event)?;
8089
if let Some(root) = close_node(node, &mut stack)? {
90+
root_seen = true;
8191
output = root;
8292
}
8393
}
@@ -86,6 +96,8 @@ pub fn parse_xml(xml: &str) -> Result<Value, XmlParseError> {
8696
Ok(Event::CData(event)) => {
8797
if let Some(node) = stack.last_mut() {
8898
node.text.push_str(&event.decode()?);
99+
} else {
100+
return Err(XmlParseError("CDATA outside root element".to_string()));
89101
}
90102
}
91103
Ok(Event::End(event)) => {
@@ -100,6 +112,7 @@ pub fn parse_xml(xml: &str) -> Result<Value, XmlParseError> {
100112
)));
101113
}
102114
if let Some(root) = close_node(node, &mut stack)? {
115+
root_seen = true;
103116
output = root;
104117
}
105118
}
@@ -144,6 +157,8 @@ fn node_from_start(reader: &Reader<&[u8]>, event: &BytesStart<'_>) -> Result<Nod
144157
fn append_text(stack: &mut [Node], event: BytesText<'_>) -> Result<(), XmlParseError> {
145158
if let Some(node) = stack.last_mut() {
146159
node.text.push_str(&event.decode()?);
160+
} else if !event.decode()?.chars().all(char::is_whitespace) {
161+
return Err(XmlParseError("text outside root element".to_string()));
147162
}
148163
Ok(())
149164
}
@@ -159,6 +174,10 @@ fn append_ref(stack: &mut [Node], event: BytesRef<'_>) -> Result<(), XmlParseErr
159174
})?;
160175
node.text.push_str(resolved);
161176
}
177+
} else {
178+
return Err(XmlParseError(
179+
"entity reference outside root element".to_string(),
180+
));
162181
}
163182
Ok(())
164183
}

src/xml2json_test.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,22 @@ fn rejects_unclosed_elements_at_end_of_input() {
9797
assert!(err.contains("unexpected end of input"));
9898
}
9999

100+
#[test]
101+
fn rejects_multiple_root_elements() {
102+
let err = parse_xml("<foo>one</foo><bar>two</bar>")
103+
.unwrap_err()
104+
.to_string();
105+
106+
assert!(err.contains("multiple root elements"));
107+
}
108+
109+
#[test]
110+
fn rejects_text_outside_root_element() {
111+
let err = parse_xml("text<foo>bar</foo>").unwrap_err().to_string();
112+
113+
assert!(err.contains("text outside root element"));
114+
}
115+
100116
#[test]
101117
fn rejects_unknown_entity_references() {
102118
let err = parse_xml("<foo>&unknown;</foo>").unwrap_err().to_string();

tests/golden.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,26 @@ json["123numeric"] = "value4";
917917
r#"{"123numeric":"value4","key with spaces":"value3","key-with-dashes":"value1","key.with.dots":"value2"}"#
918918
);
919919

920+
#[cfg(feature = "greppable")]
921+
test!(
922+
from_gron_single_quoted_path_segment,
923+
&["--from-gron", "-S", "this"],
924+
r#"json = {};
925+
json['User-Agent'] = "gron/0.1";
926+
"#,
927+
r#"{"User-Agent":"gron/0.1"}"#
928+
);
929+
930+
#[cfg(feature = "greppable")]
931+
test!(
932+
from_gron_single_quoted_value,
933+
&["--from-gron", "-S", "this"],
934+
r#"json = {};
935+
json.value = 'single quoted';
936+
"#,
937+
r#"{"value":"single quoted"}"#
938+
);
939+
920940
#[cfg(feature = "greppable")]
921941
test!(
922942
from_gron_deeply_nested,

0 commit comments

Comments
 (0)