use rupg_common::SqlState;
use crate::error::TypeError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Kind {
ObjectStart,
ObjectEnd,
ArrayStart,
ArrayEnd,
Comma,
Colon,
String,
Number,
True,
False,
Null,
End,
}
#[derive(Debug, Clone, Copy)]
struct Token {
kind: Kind,
start: usize,
end: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Error {
InvalidToken,
EscapingInvalid,
EscapingRequired,
UnicodeEscapeFormat,
ExpectedEnd,
ExpectedArrayNext,
ExpectedColon,
ExpectedJson,
ExpectedMore,
ExpectedObjectFirst,
ExpectedObjectNext,
ExpectedString,
CodePointZero,
HighSurrogate,
LowSurrogate,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Fail {
error: Error,
start: usize,
end: usize,
}
fn alphanumeric(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_' || b >= 0x80
}
struct Lexer<'a> {
input: &'a [u8],
at: usize,
escapes: bool,
text: String,
}
impl Lexer<'_> {
fn next(&mut self) -> Result<Token, Fail> {
let b = self.input;
while self.at < b.len() && matches!(b[self.at], b' ' | b'\t' | b'\n' | b'\r') {
self.at += 1;
}
let start = self.at;
let token = |kind, end| Token { kind, start, end };
let Some(&first) = b.get(start) else {
return Ok(token(Kind::End, start));
};
let kind = match first {
b'{' => Kind::ObjectStart,
b'}' => Kind::ObjectEnd,
b'[' => Kind::ArrayStart,
b']' => Kind::ArrayEnd,
b',' => Kind::Comma,
b':' => Kind::Colon,
b'"' => {
self.at = self.string(start)?;
return Ok(token(Kind::String, self.at));
}
b'-' => {
self.at = self.number(start, start + 1)?;
return Ok(token(Kind::Number, self.at));
}
b'0'..=b'9' => {
self.at = self.number(start, start)?;
return Ok(token(Kind::Number, self.at));
}
_ => {
let end = start + b[start..].iter().take_while(|&&c| alphanumeric(c)).count();
if end == start {
return Err(Fail { error: Error::InvalidToken, start, end: start + 1 });
}
self.at = end;
return match &b[start..end] {
b"true" => Ok(token(Kind::True, end)),
b"false" => Ok(token(Kind::False, end)),
b"null" => Ok(token(Kind::Null, end)),
_ => Err(Fail { error: Error::InvalidToken, start, end }),
};
}
};
self.at = start + 1;
Ok(token(kind, self.at))
}
fn char_end(&self, at: usize) -> usize {
let len = match self.input[at] {
b if b < 0x80 => 1,
b if b & 0xe0 == 0xc0 => 2,
b if b & 0xf0 == 0xe0 => 3,
_ => 4,
};
(at + len).min(self.input.len())
}
fn string(&mut self, start: usize) -> Result<usize, Fail> {
let b = self.input;
let unterminated = Fail { error: Error::InvalidToken, start, end: b.len() };
let escapes = self.escapes;
self.text.clear();
let mut high: Option<u32> = None;
let mut s = start + 1;
loop {
let run = b[s..].iter().position(|&c| c == b'"' || c == b'\\' || c < 32);
let Some(run) = run else {
return Err(unterminated);
};
if escapes && run > 0 {
if high.is_some() {
let end = self.char_end(s);
return Err(Fail { error: Error::LowSurrogate, start, end });
}
self.text.push_str(std::str::from_utf8(&b[s..s + run]).unwrap_or_default());
}
s += run;
match b[s] {
b'"' => {
if high.is_some() {
return Err(Fail { error: Error::LowSurrogate, start, end: s + 1 });
}
return Ok(s + 1);
}
b'\\' => {
s += 1;
match b.get(s) {
None => return Err(unterminated),
Some(b'u') => {
let mut ch = 0u32;
for _ in 0..4 {
s += 1;
match b.get(s) {
None => return Err(unterminated),
Some(c) if c.is_ascii_hexdigit() => {
ch = ch * 16 + char::from(*c).to_digit(16).unwrap_or(0);
}
Some(_) => {
let end = self.char_end(s);
return Err(Fail {
error: Error::UnicodeEscapeFormat,
start,
end,
});
}
}
}
if escapes {
let fail = |error| Fail { error, start, end: s + 1 };
if (0xd800..=0xdbff).contains(&ch) {
if high.is_some() {
return Err(fail(Error::HighSurrogate));
}
high = Some(ch);
s += 1;
continue;
}
if (0xdc00..=0xdfff).contains(&ch) {
let Some(first) = high.take() else {
return Err(fail(Error::LowSurrogate));
};
ch = 0x10000 + ((first & 0x3ff) << 10) + (ch & 0x3ff);
}
if high.is_some() {
return Err(fail(Error::LowSurrogate));
}
if ch == 0 {
return Err(fail(Error::CodePointZero));
}
self.text.push(char::from_u32(ch).unwrap_or('\u{fffd}'));
}
}
Some(&c @ (b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't')) => {
if escapes {
if high.is_some() {
return Err(Fail {
error: Error::LowSurrogate,
start,
end: s + 1,
});
}
self.text.push(match c {
b'b' => '\u{8}',
b'f' => '\u{c}',
b'n' => '\n',
b'r' => '\r',
b't' => '\t',
other => char::from(other),
});
}
}
Some(_) => {
if escapes && high.is_some() {
let end = self.char_end(s);
return Err(Fail { error: Error::LowSurrogate, start, end });
}
let end = self.char_end(s);
return Err(Fail { error: Error::EscapingInvalid, start: s, end });
}
}
s += 1;
}
_ => return Err(Fail { error: Error::EscapingRequired, start, end: s }),
}
}
}
fn number(&self, start: usize, mut s: usize) -> Result<usize, Fail> {
let b = self.input;
let digit = |s: usize| b.get(s).is_some_and(u8::is_ascii_digit);
let digits = |mut s: usize| {
while digit(s) {
s += 1;
}
s
};
let mut error = false;
match b.get(s) {
Some(b'0') => s += 1,
Some(b'1'..=b'9') => s = digits(s),
_ => error = true,
}
if b.get(s) == Some(&b'.') {
s += 1;
if digit(s) { s = digits(s) } else { error = true }
}
if matches!(b.get(s), Some(b'e' | b'E')) {
s += 1;
if matches!(b.get(s), Some(b'+' | b'-')) {
s += 1;
}
if digit(s) { s = digits(s) } else { error = true }
}
while b.get(s).is_some_and(|&c| alphanumeric(c)) {
s += 1;
error = true;
}
if error {
return Err(Fail { error: Error::InvalidToken, start, end: s });
}
Ok(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Open {
Array,
Object,
}
enum Step {
Value,
Field,
After,
}
fn unexpected(token: Token, error: Error) -> Fail {
let error = if token.kind == Kind::End { Error::ExpectedMore } else { error };
Fail { error, start: token.start, end: token.end }
}
pub(crate) trait Sink {
fn open(&mut self, open: Open) -> Result<(), TypeError>;
fn close(&mut self, open: Open) -> Result<(), TypeError>;
fn key(&mut self, key: &str) -> Result<(), TypeError>;
fn element(&mut self, null: bool) -> Result<(), TypeError> {
let _ = null;
Ok(())
}
fn scalar(&mut self, kind: Kind, token: &str, text: &str) -> Result<(), TypeError>;
fn open_at(&mut self, open: Open, start: usize) -> Result<(), TypeError> {
let _ = start;
self.open(open)
}
fn close_at(&mut self, open: Open, end: usize) -> Result<(), TypeError> {
let _ = end;
self.close(open)
}
fn scalar_at(
&mut self,
kind: Kind,
start: usize,
token: &str,
text: &str,
) -> Result<(), TypeError> {
let _ = start;
self.scalar(kind, token, text)
}
}
pub(crate) enum Stop {
Syntax(Fail),
Action(TypeError),
}
impl From<Fail> for Stop {
fn from(fail: Fail) -> Stop {
Stop::Syntax(fail)
}
}
struct Check;
impl Sink for Check {
fn open(&mut self, _: Open) -> Result<(), TypeError> {
Ok(())
}
fn close(&mut self, _: Open) -> Result<(), TypeError> {
Ok(())
}
fn key(&mut self, _: &str) -> Result<(), TypeError> {
Ok(())
}
fn scalar(&mut self, _: Kind, _: &str, _: &str) -> Result<(), TypeError> {
Ok(())
}
}
pub(crate) fn parse(input: &str, escapes: bool, sink: &mut impl Sink) -> Result<(), Stop> {
let text = |token: Token| &input[token.start..token.end];
let input = input.as_bytes();
let mut lexer = Lexer { input, at: 0, escapes, text: String::new() };
let mut stack = Vec::new();
let mut decoded = String::new();
let mut name = String::new();
let mut token = lexer.next()?;
let mut step = Step::Value;
loop {
step = match step {
Step::Value => {
if stack.last() == Some(&Open::Array) {
sink.element(token.kind == Kind::Null).map_err(Stop::Action)?;
}
match token.kind {
Kind::ObjectStart => {
stack.push(Open::Object);
sink.open_at(Open::Object, token.start).map_err(Stop::Action)?;
token = lexer.next()?;
match token.kind {
Kind::String => Step::Field,
Kind::ObjectEnd => {
stack.pop();
let end = token.end;
token = lexer.next()?;
sink.close_at(Open::Object, end).map_err(Stop::Action)?;
Step::After
}
_ => return Err(unexpected(token, Error::ExpectedObjectFirst).into()),
}
}
Kind::ArrayStart => {
sink.open_at(Open::Array, token.start).map_err(Stop::Action)?;
token = lexer.next()?;
if token.kind == Kind::ArrayEnd {
let end = token.end;
token = lexer.next()?;
sink.close_at(Open::Array, end).map_err(Stop::Action)?;
Step::After
} else {
stack.push(Open::Array);
Step::Value
}
}
Kind::String | Kind::Number | Kind::True | Kind::False | Kind::Null => {
let scalar = token;
std::mem::swap(&mut lexer.text, &mut decoded);
token = lexer.next()?;
sink.scalar_at(scalar.kind, scalar.start, text(scalar), &decoded)
.map_err(Stop::Action)?;
Step::After
}
_ => return Err(unexpected(token, Error::ExpectedJson).into()),
}
}
Step::Field => {
if token.kind != Kind::String {
return Err(unexpected(token, Error::ExpectedString).into());
}
name.clear();
name.push_str(&lexer.text);
token = lexer.next()?;
if token.kind != Kind::Colon {
return Err(unexpected(token, Error::ExpectedColon).into());
}
token = lexer.next()?;
sink.key(&name).map_err(Stop::Action)?;
Step::Value
}
Step::After => {
let Some(&open) = stack.last() else {
if token.kind != Kind::End {
return Err(unexpected(token, Error::ExpectedEnd).into());
}
return Ok(());
};
let (close, error, next) = match open {
Open::Array => (Kind::ArrayEnd, Error::ExpectedArrayNext, Step::Value),
Open::Object => (Kind::ObjectEnd, Error::ExpectedObjectNext, Step::Field),
};
if token.kind == Kind::Comma {
token = lexer.next()?;
next
} else if token.kind == close {
stack.pop();
let end = token.end;
token = lexer.next()?;
sink.close_at(open, end).map_err(Stop::Action)?;
Step::After
} else {
return Err(unexpected(token, error).into());
}
}
};
}
}
pub fn json_in(s: &str) -> Result<&str, TypeError> {
match parse(s, false, &mut Check) {
Ok(()) => Ok(s),
Err(Stop::Syntax(fail)) => Err(syntax(s, fail)),
Err(Stop::Action(error)) => Err(error),
}
}
pub(crate) fn syntax(s: &str, fail: Fail) -> TypeError {
let token = &s[fail.start..fail.end];
let detail = match fail.error {
Error::InvalidToken => format!("Token \"{token}\" is invalid."),
Error::EscapingInvalid => format!("Escape sequence \"\\{token}\" is invalid."),
Error::EscapingRequired => {
format!("Character with value 0x{:02x} must be escaped.", s.as_bytes()[fail.end])
}
Error::UnicodeEscapeFormat => {
"\"\\u\" must be followed by four hexadecimal digits.".to_string()
}
Error::ExpectedEnd => format!("Expected end of input, but found \"{token}\"."),
Error::ExpectedArrayNext => format!("Expected \",\" or \"]\", but found \"{token}\"."),
Error::ExpectedColon => format!("Expected \":\", but found \"{token}\"."),
Error::ExpectedJson => format!("Expected JSON value, but found \"{token}\"."),
Error::ExpectedMore => "The input string ended unexpectedly.".to_string(),
Error::ExpectedObjectFirst => format!("Expected string or \"}}\", but found \"{token}\"."),
Error::ExpectedObjectNext => format!("Expected \",\" or \"}}\", but found \"{token}\"."),
Error::ExpectedString => format!("Expected string, but found \"{token}\"."),
Error::CodePointZero => "\\u0000 cannot be converted to text.".to_string(),
Error::HighSurrogate => {
"Unicode high surrogate must not follow a high surrogate.".to_string()
}
Error::LowSurrogate => "Unicode low surrogate must follow a high surrogate.".to_string(),
};
let mut error = match fail.error {
Error::CodePointZero => TypeError::new(
SqlState::UNTRANSLATABLE_CHARACTER,
"unsupported Unicode escape sequence".to_string(),
),
_ => TypeError::new(
SqlState::INVALID_TEXT_REPRESENTATION,
"invalid input syntax for type json".to_string(),
),
};
error.detail = Some(detail);
error.context = Some(context(s, fail));
error
}
fn context(s: &str, fail: Fail) -> String {
let b = s.as_bytes();
let end = fail.end;
let line_start =
b[..fail.start.min(end)].iter().rposition(|&c| c == b'\n').map_or(0, |at| at + 1);
let line = 1 + b[..line_start].iter().filter(|&&c| c == b'\n').count();
let mut start = line_start;
while end - start >= 50 {
start += s[start..].chars().next().map_or(1, char::len_utf8);
}
if start - line_start <= 3 {
start = line_start;
}
let prefix = if start > line_start { "..." } else { "" };
let more = fail.error != Error::ExpectedMore && end < b.len();
let suffix = if more && !matches!(b[end], b'\n' | b'\r') { "..." } else { "" };
format!("JSON data, line {line}: {prefix}{}{suffix}", &s[start..end])
}
#[cfg(test)]
mod tests {
use super::*;
fn detail(s: &str) -> String {
json_in(s).unwrap_err().detail.unwrap()
}
#[test]
fn the_first_error_is_the_error_of_postgres() {
assert!(json_in(r#" {"a": [1, -2.5e+3, true, null, "\u00e9\n", {}, []]} "#).is_ok());
assert_eq!(detail("[1 2]"), r#"Expected "," or "]", but found "2"."#);
assert_eq!(detail("[1, 2x]"), r#"Token "2x" is invalid."#);
assert_eq!(detail(r#"{"a" 1}"#), r#"Expected ":", but found "1"."#);
assert_eq!(detail(r#"{"a": 1,}"#), r#"Expected string, but found "}"."#);
assert_eq!(detail("[1,"), "The input string ended unexpectedly.");
assert_eq!(detail(r#""\é""#), r#"Escape sequence "\é" is invalid."#);
assert_eq!(detail("\"a\tb\""), "Character with value 0x09 must be escaped.");
assert_eq!(detail(r#""abc"#), r#"Token ""abc" is invalid."#);
let context = |s: &str| json_in(s).unwrap_err().context.unwrap();
assert_eq!(context("[1 2]"), "JSON data, line 1: [1 2...");
assert_eq!(context("[1,"), "JSON data, line 1: [1,");
assert_eq!(context("[1,\n 2,\n x]"), "JSON data, line 3: x...");
let long = format!("[{}x]", "1, ".repeat(30));
assert_eq!(context(&long), format!("JSON data, line 1: ...{}...", &long[43..92]));
}
#[test]
fn deep_nesting_needs_no_recursion() {
let deep = format!("{}{}", "[".repeat(100_000), "]".repeat(100_000));
assert!(json_in(&deep).is_ok());
}
}