#![allow(
clippy::unwrap_used,
clippy::panic,
reason = "unwraps and panics are allowed in tests"
)]
use std::borrow::Cow;
use std::assert_matches;
use super::{eq, from_raw, Warning};
use crate::{json, warning::CaveatDeferred};
fn parse_name(s: &str) -> CaveatDeferred<Cow<'_, str>, Warning> {
let doc = json::parse(s.into()).unwrap();
let elem = doc.root().find_field("name").unwrap().element();
let s = elem.to_raw_str().unwrap().as_unescaped_str();
from_raw(s)
}
#[test]
fn should_unescape_empty_str() {
const INPUT: &str = r#"{ "name": "" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(""));
assert!(warnings.is_empty(), "{:#?}", warnings.id_map());
}
#[test]
fn should_unescape_str_without_escapes() {
const INPUT: &str = r#"{ "name": "ab" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert!(warnings.is_empty(), "{:#?}", warnings.id_map());
}
#[test]
fn should_unescape_forward_slash_escape() {
const INPUT: &str = r#"{ "name": "a\/b" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
let Cow::Owned(s) = string else {
panic!("expected an owned Cow, got {string:?}");
};
assert_eq!(s, "a/b");
assert!(warnings.is_empty(), "{:#?}", warnings.id_map());
}
#[test]
fn should_unescape_backslash_escape() {
const INPUT: &str = r#"{ "name": "a\\b" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
let Cow::Owned(s) = string else {
panic!("expected an owned Cow, got {string:?}");
};
assert_eq!(s, r"a\b");
assert!(warnings.is_empty(), "{:#?}", warnings.id_map());
}
#[test]
fn should_unescape_quote_escape() {
const INPUT: &str = r#"{ "name": "a\"b" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
let Cow::Owned(s) = string else {
panic!("expected an owned Cow, got {string:?}");
};
assert_eq!(s, r#"a"b"#);
assert!(warnings.is_empty(), "{:#?}", warnings.id_map());
}
#[test]
fn should_unescape_str_with_many_escapes() {
const INPUT: &str = r#"{"name": "a\/\"b\"" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
let Cow::Owned(s) = string else {
panic!("expected an owned Cow, got {string:?}");
};
assert_eq!(s, r#"a/"b""#);
assert!(warnings.is_empty(), "{:#?}", warnings.id_map());
}
#[test]
fn should_fail_to_unescape_raw_str_with_rust_unicode_literal_control_char() {
const INPUT: &str = r#"{ "name": "hello\u{0019}world" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::InvalidEscape(10)]
);
}
#[test]
fn should_fail_to_unescape_json_control_escape() {
const INPUT: &str = r#"{ "name": "hello\u0019world" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::ControlCharacter(10)]
);
}
#[test]
fn should_unescape_unicode_literals() {
const INPUT: &str = r#"{ "name": "hello\u0020world\u0021" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
let Cow::Owned(s) = string else {
panic!("expected an owned Cow, got {string:?}");
};
assert_eq!(s, "hello world!");
assert!(warnings.is_empty(), "{:#?}", warnings.id_map());
}
#[test]
fn should_unescape_utf_16_surrogate_pair() {
const INPUT: &str = r#"{ "name": "hello\uD834\uDD1Eworld" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
let Cow::Owned(s) = string else {
panic!("expected an owned Cow, got {string:?}");
};
assert_eq!(s, "hello\u{1D11E}world");
assert!(warnings.is_empty(), "{:#?}", warnings.id_map());
}
#[test]
fn should_unescape_unicode_literal_followed_by_simple_escape() {
const INPUT: &str = r#"{ "name": "hello \/world!" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
let Cow::Owned(s) = string else {
panic!("expected an owned Cow, got {string:?}");
};
assert_eq!(s, "hello /world!");
assert!(warnings.is_empty(), "{:#?}", warnings.id_map());
}
#[test]
fn should_unescape_two_consecutive_bmp_unicode_escapes() {
const INPUT: &str = "{ \"name\": \"\\u0041\\u0042\" }";
let (string, warnings) = parse_name(INPUT).into_parts();
let Cow::Owned(s) = string else {
panic!("expected an owned Cow, got {string:?}");
};
assert_eq!(s, "AB");
assert!(warnings.is_empty(), "{:#?}", warnings.id_map());
}
#[test]
fn should_unescape_bmp_unicode_escape_followed_by_simple_escape() {
const INPUT: &str = "{ \"name\": \"\\u0041\\/B\" }";
let (string, warnings) = parse_name(INPUT).into_parts();
let Cow::Owned(s) = string else {
panic!("expected an owned Cow, got {string:?}");
};
assert_eq!(s, "A/B");
assert!(warnings.is_empty(), "{:#?}", warnings.id_map());
}
#[test]
fn should_warn_on_backspace_escape() {
const INPUT: &str = r#"{ "name": "a\bb" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::ControlCharacter(_)]
);
}
#[test]
fn should_warn_on_form_feed_escape() {
const INPUT: &str = r#"{ "name": "a\fb" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::ControlCharacter(_)]
);
}
#[test]
fn should_warn_on_newline_escape() {
const INPUT: &str = r#"{ "name": "a\nb" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::ControlCharacter(_)]
);
}
#[test]
fn should_warn_on_carriage_return_escape() {
const INPUT: &str = r#"{ "name": "a\rb" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::ControlCharacter(_)]
);
}
#[test]
fn should_warn_on_tab_escape() {
const INPUT: &str = r#"{ "name": "a\tb" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::ControlCharacter(_)]
);
}
#[test]
fn should_warn_on_bare_control_char() {
const INPUT: &str = "{ \"name\": \"a\tb\" }";
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::ControlCharacter(_)]
);
}
#[test]
fn should_warn_on_unicode_escape_to_control_char() {
const INPUT: &str = r#"{ "name": " " }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::ControlCharacter(_)]
);
}
#[test]
fn should_warn_on_invalid_escape() {
const INPUT: &str = r#"{ "name": "a\zb" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::InvalidEscape(_)]
);
}
#[test]
fn should_warn_on_lone_high_surrogate() {
const INPUT: &str = r#"{ "name": "\uD800" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::InvalidEscape(_)]
);
}
#[test]
fn should_warn_on_incomplete_unicode_escape() {
const INPUT: &str = r#"{ "name": "\u00" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::UnexpectedEndOfString(_)]
);
}
#[test]
fn should_warn_on_double_high_surrogate_pair() {
const INPUT: &str = r#"{ "name": "\uD834\uD834" }"#;
let (string, warnings) = parse_name(INPUT).into_parts();
assert_matches!(string, Cow::Borrowed(_));
assert_matches!(
warnings.into_warnings().as_slice(),
[Warning::DecodeUtf16(_, _)]
);
}
#[test]
fn eq_matches_plain_string() {
assert_eq!(eq("country_code", "country_code"), Ok(true));
}
#[test]
fn eq_reports_mismatch() {
assert_eq!(eq("country_code", "currency"), Ok(false));
}
#[test]
fn eq_reports_length_mismatch() {
assert_eq!(eq("country", "country_code"), Ok(false));
assert_eq!(eq("country_code", "country"), Ok(false));
}
#[test]
fn eq_matches_through_unicode_escape() {
assert_eq!(eq(r"countr\u0079_code", "country_code"), Ok(true));
}
#[test]
fn eq_matches_through_surrogate_pair() {
assert_eq!(eq(r"a\uD834\uDD1Eb", "a\u{1D11E}b"), Ok(true));
}
#[test]
fn eq_errors_on_invalid_escape() {
assert_eq!(eq(r"a\zb", "anything"), Err(Warning::InvalidEscape(2)));
}
#[test]
fn eq_short_circuits_before_later_decode_error() {
assert_eq!(eq(r"x\z", "y"), Ok(false));
}