use core::fmt;
use std::collections::HashSet;
use super::value::JsonValue;
pub const MAX_NESTING_DEPTH: usize = 64;
pub const MAX_INPUT_LEN: usize = 10 * 1024 * 1024;
#[derive(Debug, Clone, PartialEq, Eq)]
enum JsonParseErrorKind {
InputTooLarge,
MaxDepthExceeded,
UnexpectedEof,
UnexpectedCharacter,
InvalidEscape,
InvalidUnicodeEscape,
InvalidNumber,
DuplicateKey,
TrailingCharacters,
InvalidUtf8,
}
#[doc(alias = "json_error")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JsonParseError {
kind: JsonParseErrorKind,
position: usize,
}
impl JsonParseError {
#[must_use]
pub fn position(&self) -> usize {
self.position
}
}
impl fmt::Display for JsonParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
JsonParseErrorKind::InputTooLarge => {
write!(f, "json: input exceeds maximum length")
}
JsonParseErrorKind::MaxDepthExceeded => {
write!(f, "json: maximum nesting depth exceeded")
}
JsonParseErrorKind::UnexpectedEof => {
write!(
f,
"json: unexpected end of input at position {}",
self.position
)
}
JsonParseErrorKind::UnexpectedCharacter => {
write!(
f,
"json: unexpected character at position {}",
self.position
)
}
JsonParseErrorKind::InvalidEscape => {
write!(
f,
"json: invalid escape sequence at position {}",
self.position
)
}
JsonParseErrorKind::InvalidUnicodeEscape => {
write!(
f,
"json: invalid unicode escape at position {}",
self.position
)
}
JsonParseErrorKind::InvalidNumber => {
write!(f, "json: invalid number at position {}", self.position)
}
JsonParseErrorKind::DuplicateKey => {
write!(f, "json: duplicate key at position {}", self.position)
}
JsonParseErrorKind::TrailingCharacters => {
write!(f, "json: trailing characters at position {}", self.position)
}
JsonParseErrorKind::InvalidUtf8 => {
write!(f, "json: invalid UTF-8 at position {}", self.position)
}
}
}
}
impl std::error::Error for JsonParseError {}
struct Parser<'a> {
input: &'a [u8],
pos: usize,
depth: usize,
}
impl<'a> Parser<'a> {
fn new(input: &'a [u8]) -> Self {
Self {
input,
pos: 0,
depth: 0,
}
}
#[inline]
fn peek(&self) -> Option<u8> {
self.input.get(self.pos).copied()
}
#[inline]
fn next_byte(&mut self) -> Result<u8, JsonParseError> {
if self.pos >= self.input.len() {
return Err(self.error(JsonParseErrorKind::UnexpectedEof));
}
let b = self.input[self.pos];
self.pos += 1;
Ok(b)
}
#[inline]
fn skip_whitespace(&mut self) {
while self.pos < self.input.len() {
match self.input[self.pos] {
b' ' | b'\t' | b'\n' | b'\r' => self.pos += 1,
_ => break,
}
}
}
fn expect(&mut self, expected: u8) -> Result<(), JsonParseError> {
let b = self.next_byte()?;
if b == expected {
Ok(())
} else {
self.pos -= 1; Err(self.error(JsonParseErrorKind::UnexpectedCharacter))
}
}
fn expect_literal(&mut self, literal: &[u8]) -> Result<(), JsonParseError> {
for &expected in literal {
let b = self.next_byte()?;
if b != expected {
self.pos -= 1;
return Err(self.error(JsonParseErrorKind::UnexpectedCharacter));
}
}
Ok(())
}
#[inline]
fn error(&self, kind: JsonParseErrorKind) -> JsonParseError {
JsonParseError {
kind,
position: self.pos,
}
}
#[inline]
fn push_depth(&mut self) -> Result<(), JsonParseError> {
self.depth += 1;
if self.depth > MAX_NESTING_DEPTH {
return Err(self.error(JsonParseErrorKind::MaxDepthExceeded));
}
Ok(())
}
#[inline]
fn pop_depth(&mut self) {
self.depth -= 1;
}
fn parse_value(&mut self) -> Result<JsonValue, JsonParseError> {
self.skip_whitespace();
match self.peek() {
Some(b'"') => self.parse_string().map(JsonValue::String),
Some(b'{') => self.parse_object(),
Some(b'[') => self.parse_array(),
Some(b't') => self.parse_true(),
Some(b'f') => self.parse_false(),
Some(b'n') => self.parse_null(),
Some(b'-' | b'0'..=b'9') => self.parse_number(),
Some(_) => Err(self.error(JsonParseErrorKind::UnexpectedCharacter)),
None => Err(self.error(JsonParseErrorKind::UnexpectedEof)),
}
}
fn parse_null(&mut self) -> Result<JsonValue, JsonParseError> {
self.expect_literal(b"null")?;
Ok(JsonValue::Null)
}
fn parse_true(&mut self) -> Result<JsonValue, JsonParseError> {
self.expect_literal(b"true")?;
Ok(JsonValue::Bool(true))
}
fn parse_false(&mut self) -> Result<JsonValue, JsonParseError> {
self.expect_literal(b"false")?;
Ok(JsonValue::Bool(false))
}
fn parse_number(&mut self) -> Result<JsonValue, JsonParseError> {
let start = self.pos;
if self.peek() == Some(b'-') {
self.pos += 1;
}
match self.peek() {
Some(b'0') => {
self.pos += 1;
if matches!(self.peek(), Some(b'0'..=b'9')) {
return Err(self.error(JsonParseErrorKind::InvalidNumber));
}
}
Some(b'1'..=b'9') => {
self.pos += 1;
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.pos += 1;
}
}
_ => return Err(self.error(JsonParseErrorKind::InvalidNumber)),
}
if self.peek() == Some(b'.') {
self.pos += 1;
if !matches!(self.peek(), Some(b'0'..=b'9')) {
return Err(self.error(JsonParseErrorKind::InvalidNumber));
}
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.pos += 1;
}
}
if matches!(self.peek(), Some(b'e' | b'E')) {
self.pos += 1;
if matches!(self.peek(), Some(b'+' | b'-')) {
self.pos += 1;
}
if !matches!(self.peek(), Some(b'0'..=b'9')) {
return Err(self.error(JsonParseErrorKind::InvalidNumber));
}
while matches!(self.peek(), Some(b'0'..=b'9')) {
self.pos += 1;
}
}
let num_str = std::str::from_utf8(&self.input[start..self.pos])
.map_err(|_| self.error(JsonParseErrorKind::InvalidNumber))?;
let value: f64 = num_str
.parse()
.map_err(|_| self.error(JsonParseErrorKind::InvalidNumber))?;
if !value.is_finite() {
return Err(self.error(JsonParseErrorKind::InvalidNumber));
}
Ok(JsonValue::Number(value))
}
fn parse_string(&mut self) -> Result<String, JsonParseError> {
self.expect(b'"')?;
let mut buf = Vec::new();
loop {
let start = self.pos;
while self.pos < self.input.len() {
let b = self.input[self.pos];
if b == b'"' || b == b'\\' || b < 0x20 {
break;
}
self.pos += 1;
}
if self.pos > start {
buf.extend_from_slice(&self.input[start..self.pos]);
}
let b = self.next_byte()?;
match b {
b'"' => break,
b'\\' => {
let escaped = self.next_byte()?;
match escaped {
b'"' => buf.push(b'"'),
b'\\' => buf.push(b'\\'),
b'/' => buf.push(b'/'),
b'b' => buf.push(0x08),
b'f' => buf.push(0x0C),
b'n' => buf.push(b'\n'),
b'r' => buf.push(b'\r'),
b't' => buf.push(b'\t'),
b'u' => {
let cp = self.parse_unicode_escape()?;
if (0xD800..=0xDBFF).contains(&cp) {
self.expect(b'\\')?;
self.expect(b'u')?;
let low = self.parse_unicode_escape()?;
if !(0xDC00..=0xDFFF).contains(&low) {
return Err(
self.error(JsonParseErrorKind::InvalidUnicodeEscape)
);
}
let combined = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00);
let ch = char::from_u32(combined).ok_or_else(|| {
self.error(JsonParseErrorKind::InvalidUnicodeEscape)
})?;
let mut utf8_buf = [0u8; 4];
buf.extend_from_slice(ch.encode_utf8(&mut utf8_buf).as_bytes());
} else if (0xDC00..=0xDFFF).contains(&cp) {
return Err(self.error(JsonParseErrorKind::InvalidUnicodeEscape));
} else {
let ch = char::from_u32(cp).ok_or_else(|| {
self.error(JsonParseErrorKind::InvalidUnicodeEscape)
})?;
let mut utf8_buf = [0u8; 4];
buf.extend_from_slice(ch.encode_utf8(&mut utf8_buf).as_bytes());
}
}
_ => {
return Err(self.error(JsonParseErrorKind::InvalidEscape));
}
}
}
b if b < 0x20 => {
return Err(self.error(JsonParseErrorKind::UnexpectedCharacter));
}
_ => buf.push(b),
}
}
String::from_utf8(buf).map_err(|_| self.error(JsonParseErrorKind::InvalidUtf8))
}
fn parse_unicode_escape(&mut self) -> Result<u32, JsonParseError> {
let mut cp: u32 = 0;
for _ in 0..4 {
let b = self.next_byte()?;
let digit = match b {
b'0'..=b'9' => u32::from(b - b'0'),
b'a'..=b'f' => u32::from(b - b'a') + 10,
b'A'..=b'F' => u32::from(b - b'A') + 10,
_ => return Err(self.error(JsonParseErrorKind::InvalidUnicodeEscape)),
};
cp = (cp << 4) | digit;
}
Ok(cp)
}
fn parse_array(&mut self) -> Result<JsonValue, JsonParseError> {
self.expect(b'[')?;
self.push_depth()?;
let mut elements = Vec::new();
self.skip_whitespace();
if self.peek() == Some(b']') {
self.pos += 1;
self.pop_depth();
return Ok(JsonValue::Array(elements));
}
loop {
let value = self.parse_value()?;
elements.push(value);
self.skip_whitespace();
match self.peek() {
Some(b',') => {
self.pos += 1;
}
Some(b']') => {
self.pos += 1;
break;
}
Some(_) => return Err(self.error(JsonParseErrorKind::UnexpectedCharacter)),
None => return Err(self.error(JsonParseErrorKind::UnexpectedEof)),
}
}
self.pop_depth();
Ok(JsonValue::Array(elements))
}
fn parse_object(&mut self) -> Result<JsonValue, JsonParseError> {
self.expect(b'{')?;
self.push_depth()?;
let mut entries: Vec<(String, JsonValue)> = Vec::new();
let mut seen_keys: HashSet<String> = HashSet::new();
self.skip_whitespace();
if self.peek() == Some(b'}') {
self.pos += 1;
self.pop_depth();
return Ok(JsonValue::Object(entries));
}
loop {
self.skip_whitespace();
let key_pos = self.pos;
let key = self.parse_string()?;
if !seen_keys.insert(key.clone()) {
return Err(JsonParseError {
kind: JsonParseErrorKind::DuplicateKey,
position: key_pos,
});
}
self.skip_whitespace();
self.expect(b':')?;
let value = self.parse_value()?;
entries.push((key, value));
self.skip_whitespace();
match self.peek() {
Some(b',') => {
self.pos += 1;
}
Some(b'}') => {
self.pos += 1;
break;
}
Some(_) => return Err(self.error(JsonParseErrorKind::UnexpectedCharacter)),
None => return Err(self.error(JsonParseErrorKind::UnexpectedEof)),
}
}
self.pop_depth();
Ok(JsonValue::Object(entries))
}
}
pub fn parse(input: &str) -> Result<JsonValue, JsonParseError> {
if input.len() > MAX_INPUT_LEN {
return Err(JsonParseError {
kind: JsonParseErrorKind::InputTooLarge,
position: 0,
});
}
let mut parser = Parser::new(input.as_bytes());
let value = parser.parse_value()?;
parser.skip_whitespace();
if parser.pos < parser.input.len() {
return Err(parser.error(JsonParseErrorKind::TrailingCharacters));
}
Ok(value)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt::Write as _;
#[test]
fn parse_null() {
assert_eq!(parse("null").unwrap(), JsonValue::Null);
}
#[test]
fn parse_null_with_whitespace() {
assert_eq!(parse(" null ").unwrap(), JsonValue::Null);
}
#[test]
fn parse_true() {
assert_eq!(parse("true").unwrap(), JsonValue::Bool(true));
}
#[test]
fn parse_false() {
assert_eq!(parse("false").unwrap(), JsonValue::Bool(false));
}
#[test]
fn parse_invalid_literal() {
assert!(parse("tru").is_err());
assert!(parse("fals").is_err());
assert!(parse("nul").is_err());
}
#[test]
fn parse_integer_zero() {
let v = parse("0").unwrap();
assert_eq!(v.as_f64(), Some(0.0));
assert_eq!(v.as_i64(), Some(0));
}
#[test]
fn parse_positive_integer() {
let v = parse("42").unwrap();
assert_eq!(v.as_f64(), Some(42.0));
assert_eq!(v.as_i64(), Some(42));
}
#[test]
fn parse_negative_integer() {
let v = parse("-17").unwrap();
assert_eq!(v.as_f64(), Some(-17.0));
assert_eq!(v.as_i64(), Some(-17));
}
#[test]
fn parse_decimal_number() {
let v = parse("3.125").unwrap();
assert!((v.as_f64().unwrap() - 3.125).abs() < f64::EPSILON);
assert_eq!(v.as_i64(), None); }
#[test]
fn parse_exponent_number() {
let v = parse("1e10").unwrap();
assert_eq!(v.as_f64(), Some(1e10));
}
#[test]
fn parse_negative_exponent() {
let v = parse("5E-3").unwrap();
assert!((v.as_f64().unwrap() - 0.005).abs() < f64::EPSILON);
}
#[test]
fn parse_exponent_with_plus() {
let v = parse("1.5e+2").unwrap();
assert_eq!(v.as_f64(), Some(150.0));
}
#[test]
fn parse_rejects_leading_zeros() {
assert!(parse("01").is_err());
assert!(parse("007").is_err());
}
#[test]
fn parse_rejects_leading_plus() {
assert!(parse("+1").is_err());
}
#[test]
fn parse_rejects_trailing_dot() {
assert!(parse("1.").is_err());
}
#[test]
fn parse_rejects_trailing_e() {
assert!(parse("1e").is_err());
}
#[test]
fn parse_negative_zero() {
let v = parse("-0").unwrap();
assert_eq!(v.as_f64(), Some(0.0));
}
#[test]
fn parse_large_integer() {
let v = parse("3600").unwrap();
assert_eq!(v.as_i64(), Some(3600));
}
#[test]
fn parse_empty_string() {
assert_eq!(parse(r#""""#).unwrap(), JsonValue::String(String::new()));
}
#[test]
fn parse_simple_string() {
assert_eq!(
parse(r#""hello""#).unwrap(),
JsonValue::String("hello".to_string())
);
}
#[test]
fn parse_string_with_escapes() {
let v = parse(r#""line1\nline2\ttab""#).unwrap();
assert_eq!(v.as_str(), Some("line1\nline2\ttab"));
}
#[test]
fn parse_string_with_backslash_escape() {
let v = parse(r#""back\\slash""#).unwrap();
assert_eq!(v.as_str(), Some("back\\slash"));
}
#[test]
fn parse_string_with_quote_escape() {
let v = parse(r#""say \"hello\"""#).unwrap();
assert_eq!(v.as_str(), Some(r#"say "hello""#));
}
#[test]
fn parse_string_with_slash_escape() {
let v = parse(r#""a\/b""#).unwrap();
assert_eq!(v.as_str(), Some("a/b"));
}
#[test]
fn parse_string_with_backspace_escape() {
let v = parse(r#""a\bb""#).unwrap();
assert_eq!(v.as_str(), Some("a\x08b"));
}
#[test]
fn parse_string_with_formfeed_escape() {
let v = parse(r#""a\fb""#).unwrap();
assert_eq!(v.as_str(), Some("a\x0Cb"));
}
#[test]
fn parse_string_with_carriage_return() {
let v = parse(r#""a\rb""#).unwrap();
assert_eq!(v.as_str(), Some("a\rb"));
}
#[test]
fn parse_string_unicode_escape_basic() {
let v = parse(r#""\u0041""#).unwrap();
assert_eq!(v.as_str(), Some("A"));
}
#[test]
fn parse_string_unicode_escape_euro() {
let v = parse(r#""\u20AC""#).unwrap();
assert_eq!(v.as_str(), Some("€"));
}
#[test]
fn parse_string_unicode_surrogate_pair() {
let v = parse(r#""\uD83D\uDE00""#).unwrap();
assert_eq!(v.as_str(), Some("😀"));
}
#[test]
fn parse_string_rejects_lone_high_surrogate() {
assert!(parse(r#""\uD800""#).is_err());
}
#[test]
fn parse_string_rejects_lone_low_surrogate() {
assert!(parse(r#""\uDC00""#).is_err());
}
#[test]
fn parse_string_rejects_invalid_escape() {
assert!(parse(r#""\x41""#).is_err());
}
#[test]
fn parse_string_rejects_unescaped_control_char() {
let input = "\"hello\tworld\"";
assert!(parse(input).is_err());
}
#[test]
fn parse_string_with_multibyte_utf8() {
let v = parse(r#""日本語""#).unwrap();
assert_eq!(v.as_str(), Some("日本語"));
}
#[test]
fn parse_string_unicode_null() {
let v = parse(r#""\u0000""#).unwrap();
assert_eq!(v.as_str(), Some("\0"));
}
#[test]
fn parse_empty_array() {
let v = parse("[]").unwrap();
assert_eq!(v.as_array(), Some([].as_slice()));
}
#[test]
fn parse_array_single_element() {
let v = parse("[1]").unwrap();
let arr = v.as_array().unwrap();
assert_eq!(arr.len(), 1);
assert_eq!(arr[0].as_i64(), Some(1));
}
#[test]
fn parse_array_mixed_types() {
let v = parse(r#"[1, "two", true, null, 3.125]"#).unwrap();
let arr = v.as_array().unwrap();
assert_eq!(arr.len(), 5);
assert_eq!(arr[0].as_i64(), Some(1));
assert_eq!(arr[1].as_str(), Some("two"));
assert_eq!(arr[2].as_bool(), Some(true));
assert!(arr[3].is_null());
assert!((arr[4].as_f64().unwrap() - 3.125).abs() < f64::EPSILON);
}
#[test]
fn parse_nested_arrays() {
let v = parse("[[1, 2], [3, [4, 5]]]").unwrap();
let outer = v.as_array().unwrap();
assert_eq!(outer.len(), 2);
let inner0 = outer[0].as_array().unwrap();
assert_eq!(inner0.len(), 2);
let inner1 = outer[1].as_array().unwrap();
assert_eq!(inner1.len(), 2);
let deep = inner1[1].as_array().unwrap();
assert_eq!(deep.len(), 2);
}
#[test]
fn parse_array_with_whitespace() {
let v = parse(" [ 1 , 2 , 3 ] ").unwrap();
let arr = v.as_array().unwrap();
assert_eq!(arr.len(), 3);
}
#[test]
fn parse_empty_object() {
let v = parse("{}").unwrap();
assert_eq!(v.as_object(), Some([].as_slice()));
}
#[test]
fn parse_object_single_key() {
let v = parse(r#"{"key": "value"}"#).unwrap();
assert_eq!(v.get_str("key"), Some("value"));
}
#[test]
fn parse_object_multiple_keys() {
let v = parse(r#"{"a": 1, "b": 2, "c": 3}"#).unwrap();
assert_eq!(v.get_i64("a"), Some(1));
assert_eq!(v.get_i64("b"), Some(2));
assert_eq!(v.get_i64("c"), Some(3));
}
#[test]
fn parse_object_preserves_order() {
let v = parse(r#"{"z": 1, "a": 2, "m": 3}"#).unwrap();
let entries = v.as_object().unwrap();
assert_eq!(entries[0].0, "z");
assert_eq!(entries[1].0, "a");
assert_eq!(entries[2].0, "m");
}
#[test]
fn parse_nested_objects() {
let v = parse(r#"{"outer": {"inner": true}}"#).unwrap();
let inner = v.get("outer").unwrap();
assert_eq!(inner.get("inner").unwrap().as_bool(), Some(true));
}
#[test]
fn parse_object_with_array_value() {
let v = parse(r#"{"items": [1, 2, 3]}"#).unwrap();
let items = v.get("items").unwrap().as_array().unwrap();
assert_eq!(items.len(), 3);
}
#[test]
fn reject_duplicate_keys() {
let result = parse(r#"{"a": 1, "a": 2}"#);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.to_string().contains("duplicate key"),
"error message should mention duplicate key: {err}",
);
}
#[test]
fn reject_duplicate_keys_nested() {
let result = parse(r#"{"outer": {"x": 1, "x": 2}}"#);
assert!(result.is_err());
}
#[test]
fn nesting_depth_at_limit() {
let open: String = "[".repeat(MAX_NESTING_DEPTH);
let close: String = "]".repeat(MAX_NESTING_DEPTH);
let input = format!("{open}1{close}");
assert!(parse(&input).is_ok());
}
#[test]
fn nesting_depth_exceeds_limit() {
let open: String = "[".repeat(MAX_NESTING_DEPTH + 1);
let close: String = "]".repeat(MAX_NESTING_DEPTH + 1);
let input = format!("{open}1{close}");
let err = parse(&input).unwrap_err();
assert!(
err.to_string().contains("nesting depth"),
"error should mention nesting depth: {err}",
);
}
#[test]
fn nesting_depth_with_objects() {
let mut input = String::new();
for i in 0..=MAX_NESTING_DEPTH {
input.push('{');
write!(input, "\"k{i}\":").unwrap();
}
input.push('1');
for _ in 0..=MAX_NESTING_DEPTH {
input.push('}');
}
let err = parse(&input).unwrap_err();
assert!(err.to_string().contains("nesting depth"));
}
#[test]
fn input_length_at_limit() {
let content = "x".repeat(MAX_INPUT_LEN - 2); let input = format!("\"{content}\"");
assert!(parse(&input).is_ok());
}
#[test]
fn input_length_exceeds_limit() {
let content = "x".repeat(MAX_INPUT_LEN + 1);
let err = parse(&content).unwrap_err();
assert!(
err.to_string().contains("maximum length"),
"error should mention maximum length: {err}",
);
}
#[test]
fn whitespace_around_tokens() {
let input = r#"
{
"key" : "value" ,
"num" : 42
}
"#;
let v = parse(input).unwrap();
assert_eq!(v.get_str("key"), Some("value"));
assert_eq!(v.get_i64("num"), Some(42));
}
#[test]
fn whitespace_all_types() {
let input = " \t\n\r{ \t\n\r\"a\" \t\n\r: \t\n\r1 \t\n\r} \t\n\r";
let v = parse(input).unwrap();
assert_eq!(v.get_i64("a"), Some(1));
}
#[test]
fn reject_trailing_characters() {
let err = parse("true false").unwrap_err();
assert!(err.to_string().contains("trailing characters"));
}
#[test]
fn reject_trailing_comma_in_object() {
assert!(parse(r#"{"a": 1,}"#).is_err());
}
#[test]
fn reject_trailing_comma_in_array() {
assert!(parse("[1, 2,]").is_err());
}
#[test]
fn error_position_for_invalid_char() {
let err = parse(" @").unwrap_err();
assert_eq!(err.position(), 3);
assert!(err.to_string().contains("position 3"));
}
#[test]
fn error_position_for_unexpected_eof() {
let err = parse(r#"{"key""#).unwrap_err();
assert!(err.to_string().contains("position"));
}
#[test]
fn error_implements_std_error() {
let err: Box<dyn std::error::Error> = Box::new(parse("@").unwrap_err());
let _ = err.to_string();
}
#[test]
fn parse_oauth_token_response() {
let input = r#"{"access_token":"abc","token_type":"bearer","expires_in":3600}"#;
let v = parse(input).unwrap();
assert_eq!(v.get_str("access_token"), Some("abc"));
assert_eq!(v.get_str("token_type"), Some("bearer"));
assert_eq!(v.get_i64("expires_in"), Some(3600));
}
#[test]
fn parse_oauth_token_response_with_optional_fields() {
let input = r#"{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOk...",
"scope": "openid profile email"
}"#;
let v = parse(input).unwrap();
assert_eq!(v.get_str("access_token"), Some("eyJhbGciOi..."));
assert_eq!(v.get_str("token_type"), Some("Bearer"));
assert_eq!(v.get_i64("expires_in"), Some(3600));
assert_eq!(v.get_str("refresh_token"), Some("tGzv3JOk..."));
assert_eq!(v.get_str("scope"), Some("openid profile email"));
}
#[test]
fn parse_oauth_error_response() {
let input =
r#"{"error":"invalid_grant","error_description":"The authorization code has expired"}"#;
let v = parse(input).unwrap();
assert_eq!(v.get_str("error"), Some("invalid_grant"));
assert_eq!(
v.get_str("error_description"),
Some("The authorization code has expired"),
);
}
#[test]
fn parse_oidc_discovery_fragment() {
let input = r#"{
"issuer": "https://accounts.example.com",
"authorization_endpoint": "https://accounts.example.com/auth",
"token_endpoint": "https://accounts.example.com/token",
"jwks_uri": "https://accounts.example.com/.well-known/jwks.json",
"response_types_supported": ["code", "token", "id_token"],
"subject_types_supported": ["public"]
}"#;
let v = parse(input).unwrap();
assert_eq!(v.get_str("issuer"), Some("https://accounts.example.com"),);
let response_types = v
.get("response_types_supported")
.unwrap()
.as_array()
.unwrap();
assert_eq!(response_types.len(), 3);
assert_eq!(response_types[0].as_str(), Some("code"));
}
#[test]
fn as_i64_non_integer_float() {
let v = parse("1.5").unwrap();
assert_eq!(v.as_i64(), None);
}
#[test]
fn as_i64_large_integer() {
let v = parse("9007199254740992").unwrap(); assert_eq!(v.as_i64(), Some(9_007_199_254_740_992));
}
#[test]
fn as_i64_rejects_values_beyond_i64_range() {
let v = parse("1e19").unwrap();
assert_eq!(v.as_i64(), None);
let v = parse("-1e19").unwrap();
assert_eq!(v.as_i64(), None);
}
#[test]
fn as_i64_rejects_2_pow_63_boundary() {
let v = parse("9223372036854775808").unwrap();
assert_eq!(v.as_i64(), None);
let v = parse("-9223372036854775808").unwrap();
assert_eq!(v.as_i64(), Some(i64::MIN));
}
#[test]
fn as_i64_negative_zero_is_zero() {
let v = parse("-0").unwrap();
assert_eq!(v.as_i64(), Some(0));
}
#[test]
fn as_i64_fractional_with_exponent_rejected() {
let v = parse("1.5e0").unwrap();
assert_eq!(v.as_i64(), None);
}
#[test]
fn get_on_non_object_returns_none() {
let v = parse("[1, 2, 3]").unwrap();
assert_eq!(v.get("key"), None);
}
#[test]
fn get_str_on_non_string_returns_none() {
let v = parse(r#"{"key": 42}"#).unwrap();
assert_eq!(v.get_str("key"), None);
}
#[test]
fn is_null_true_for_null() {
assert!(parse("null").unwrap().is_null());
}
#[test]
fn is_null_false_for_others() {
assert!(!parse("0").unwrap().is_null());
assert!(!parse("false").unwrap().is_null());
assert!(!parse(r#""""#).unwrap().is_null());
}
#[test]
fn get_missing_key_returns_none() {
let v = parse(r#"{"a": 1}"#).unwrap();
assert_eq!(v.get("b"), None);
assert_eq!(v.get_str("b"), None);
assert_eq!(v.get_i64("b"), None);
}
#[test]
fn parse_just_a_string() {
let v = parse(r#""hello world""#).unwrap();
assert_eq!(v.as_str(), Some("hello world"));
}
#[test]
fn parse_empty_input_fails() {
assert!(parse("").is_err());
}
#[test]
fn parse_whitespace_only_fails() {
assert!(parse(" ").is_err());
}
#[test]
fn parse_rejects_bare_comma() {
assert!(parse(",").is_err());
}
#[test]
fn parse_rejects_bare_colon() {
assert!(parse(":").is_err());
}
#[test]
fn parse_object_with_empty_string_key() {
let v = parse(r#"{"": "empty key"}"#).unwrap();
assert_eq!(v.get_str(""), Some("empty key"));
}
}