mirror of
https://github.com/vlang/v.git
synced 2025-08-03 09:47:15 -04:00
decoder2: fix number decoding and improve errors (#25015)
This commit is contained in:
parent
c49b9da04e
commit
f03d800800
@ -90,8 +90,12 @@ fn test_required_attribute() {
|
||||
mut has_error := false
|
||||
|
||||
json.decode[StruWithRequiredAttribute]('{"name": "hola", "a": 2, "b": 3}') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 31
|
||||
assert err.message == 'Data: missing required field `skip_and_required`'
|
||||
}
|
||||
has_error = true
|
||||
assert err.msg() == 'missing required field `skip_and_required`'
|
||||
}
|
||||
|
||||
assert has_error, '`required` attribute not working. It should have failed'
|
||||
|
@ -149,34 +149,152 @@ pub enum ValueKind {
|
||||
null
|
||||
}
|
||||
|
||||
// error generates an error message with context from the JSON string.
|
||||
fn (mut checker Decoder) error(message string) ! {
|
||||
json := if checker.json.len < checker.checker_idx + 5 {
|
||||
checker.json
|
||||
} else {
|
||||
checker.json[0..checker.checker_idx + 5]
|
||||
const max_context_lenght = 50
|
||||
const max_extra_charaters = 5
|
||||
const tab_width = 8
|
||||
|
||||
pub struct JsonDecodeError {
|
||||
Error
|
||||
context string
|
||||
pub:
|
||||
message string
|
||||
|
||||
line int
|
||||
character int
|
||||
}
|
||||
|
||||
fn (e JsonDecodeError) msg() string {
|
||||
return '\n${e.line}:${e.character}: Invalid json: ${e.message}\n${e.context}'
|
||||
}
|
||||
|
||||
// checker_error generates a checker error message showing the position in the json string
|
||||
fn (mut checker Decoder) checker_error(message string) ! {
|
||||
position := checker.checker_idx
|
||||
|
||||
mut line_number := 0
|
||||
mut character_number := 0
|
||||
mut last_newline := 0
|
||||
|
||||
for i := position - 1; i >= 0; i-- {
|
||||
if last_newline == 0 {
|
||||
if checker.json[i] == `\n` {
|
||||
last_newline = i + 1
|
||||
} else if checker.json[i] == `\t` {
|
||||
character_number += tab_width
|
||||
} else {
|
||||
character_number++
|
||||
}
|
||||
}
|
||||
if checker.json[i] == `\n` {
|
||||
line_number++
|
||||
}
|
||||
}
|
||||
|
||||
mut error_message := '\n'
|
||||
last_new_line := json.last_index_u8(`\n`)
|
||||
if last_new_line != -1 {
|
||||
error_message += json[last_new_line..checker.checker_idx]
|
||||
} else {
|
||||
error_message += json[0..checker.checker_idx]
|
||||
}
|
||||
error_message += [json[checker.checker_idx]].bytestr()
|
||||
cutoff := character_number > max_context_lenght
|
||||
|
||||
error_message += '\n'
|
||||
// either start of string, last newline or a limited amount of characters
|
||||
context_start := if cutoff { position - max_context_lenght } else { last_newline }
|
||||
|
||||
if last_new_line != -1 {
|
||||
error_message += ' '.repeat(checker.checker_idx - last_new_line)
|
||||
} else {
|
||||
error_message += ' '.repeat(checker.checker_idx)
|
||||
// print some extra characters
|
||||
mut context_end := int_min(checker.json.len, position + max_extra_charaters)
|
||||
context_end_newline := checker.json[position..context_end].index_u8(`\n`)
|
||||
|
||||
if context_end_newline != -1 {
|
||||
context_end = position + context_end_newline
|
||||
}
|
||||
|
||||
error_message += '^ ${message}'
|
||||
mut context := ''
|
||||
|
||||
return error(error_message)
|
||||
if cutoff {
|
||||
context += '...'
|
||||
}
|
||||
context += checker.json[context_start..position]
|
||||
context += '\e[31m${checker.json[position].ascii_str()}\e[0m'
|
||||
context += checker.json[position + 1..context_end]
|
||||
context += '\n'
|
||||
|
||||
if cutoff {
|
||||
context += ' '.repeat(max_context_lenght + 3)
|
||||
} else {
|
||||
context += ' '.repeat(character_number)
|
||||
}
|
||||
context += '\e[31m^\e[0m'
|
||||
|
||||
return JsonDecodeError{
|
||||
context: context
|
||||
message: 'Syntax: ${message}'
|
||||
line: line_number + 1
|
||||
character: character_number + 1
|
||||
}
|
||||
}
|
||||
|
||||
// decode_error generates a decoding error from the decoding stage
|
||||
fn (mut decoder Decoder) decode_error(message string) ! {
|
||||
mut error_info := ValueInfo{}
|
||||
if decoder.current_node != unsafe { nil } {
|
||||
error_info = decoder.current_node.value
|
||||
} else {
|
||||
error_info = decoder.values_info.tail.value
|
||||
}
|
||||
|
||||
start := error_info.position
|
||||
end := start + int_min(error_info.length, max_context_lenght)
|
||||
|
||||
mut line_number := 0
|
||||
mut character_number := 0
|
||||
mut last_newline := 0
|
||||
|
||||
for i := start - 1; i >= 0; i-- {
|
||||
if last_newline == 0 {
|
||||
if decoder.json[i] == `\n` {
|
||||
last_newline = i + 1
|
||||
} else if decoder.json[i] == `\t` {
|
||||
character_number += tab_width
|
||||
} else {
|
||||
character_number++
|
||||
}
|
||||
}
|
||||
if decoder.json[i] == `\n` {
|
||||
line_number++
|
||||
}
|
||||
}
|
||||
|
||||
cutoff := character_number > max_context_lenght
|
||||
|
||||
// either start of string, last newline or a limited amount of characters
|
||||
context_start := if cutoff { start - max_context_lenght } else { last_newline }
|
||||
|
||||
// print some extra characters
|
||||
mut context_end := int_min(decoder.json.len, end + max_extra_charaters)
|
||||
context_end_newline := decoder.json[end..context_end].index_u8(`\n`)
|
||||
|
||||
if context_end_newline != -1 {
|
||||
context_end = end + context_end_newline
|
||||
}
|
||||
|
||||
mut context := ''
|
||||
|
||||
if cutoff {
|
||||
context += '...'
|
||||
}
|
||||
context += decoder.json[context_start..start]
|
||||
context += '\e[31m${decoder.json[start..end]}\e[0m'
|
||||
context += decoder.json[end..context_end]
|
||||
context += '\n'
|
||||
|
||||
if cutoff {
|
||||
context += ' '.repeat(max_context_lenght + 3)
|
||||
} else {
|
||||
context += ' '.repeat(character_number)
|
||||
}
|
||||
context += '\e[31m${'~'.repeat(error_info.length)}\e[0m'
|
||||
|
||||
return JsonDecodeError{
|
||||
context: context
|
||||
message: 'Data: ${message}'
|
||||
line: line_number + 1
|
||||
character: character_number + 1
|
||||
}
|
||||
}
|
||||
|
||||
// check_json_format checks if the JSON string is valid and updates the decoder state.
|
||||
@ -184,7 +302,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
checker_end := checker.json.len
|
||||
// check if the JSON string is empty
|
||||
if val == '' {
|
||||
return checker.error('empty string')
|
||||
return checker.checker_error('empty string')
|
||||
}
|
||||
|
||||
// skip whitespace
|
||||
@ -206,12 +324,12 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
mut actual_value_info_pointer := checker.values_info.last()
|
||||
match value_kind {
|
||||
.unknown {
|
||||
return checker.error('unknown value kind')
|
||||
return checker.checker_error('unknown value kind')
|
||||
}
|
||||
.null {
|
||||
// check if the JSON string is a null value
|
||||
if checker_end - checker.checker_idx <= 3 {
|
||||
return checker.error('EOF error: expecting `null`')
|
||||
return checker.checker_error('EOF error: expecting `null`')
|
||||
}
|
||||
|
||||
is_not_ok := unsafe {
|
||||
@ -219,22 +337,17 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
}
|
||||
|
||||
if is_not_ok != 0 {
|
||||
return checker.error('invalid null value. Got `${checker.json[checker.checker_idx..
|
||||
return checker.checker_error('invalid null value. Got `${checker.json[checker.checker_idx..
|
||||
checker.checker_idx + 4]}` instead of `null`')
|
||||
}
|
||||
checker.checker_idx += 3
|
||||
}
|
||||
.object {
|
||||
if checker_end - checker.checker_idx < 2 {
|
||||
return checker.error('EOF error: expecting a complete object after `{`')
|
||||
return checker.checker_error('EOF error: expecting a complete object after `{`')
|
||||
}
|
||||
checker.checker_idx++
|
||||
for val[checker.checker_idx] != `}` {
|
||||
// check if the JSON string is an empty object
|
||||
if checker_end - checker.checker_idx <= 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
// skip whitespace
|
||||
for val[checker.checker_idx] in whitespace_chars {
|
||||
if checker.checker_idx >= checker_end - 1 {
|
||||
@ -248,7 +361,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
}
|
||||
|
||||
if val[checker.checker_idx] != `"` {
|
||||
return checker.error('Expecting object key')
|
||||
return checker.checker_error('Expecting object key')
|
||||
}
|
||||
|
||||
// Object key
|
||||
@ -256,16 +369,16 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
|
||||
for val[checker.checker_idx] != `:` {
|
||||
if checker.checker_idx >= checker_end - 1 {
|
||||
return checker.error('EOF error: key colon not found')
|
||||
return checker.checker_error('EOF error: key colon not found')
|
||||
}
|
||||
if val[checker.checker_idx] !in whitespace_chars {
|
||||
return checker.error('invalid value after object key')
|
||||
return checker.checker_error('invalid value after object key')
|
||||
}
|
||||
checker.checker_idx++
|
||||
}
|
||||
|
||||
if val[checker.checker_idx] != `:` {
|
||||
return checker.error('Expecting `:` after object key')
|
||||
return checker.checker_error('Expecting `:` after object key')
|
||||
}
|
||||
// skip `:`
|
||||
checker.checker_idx++
|
||||
@ -286,7 +399,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
break
|
||||
}
|
||||
if checker.checker_idx >= checker_end - 1 {
|
||||
return checker.error('EOF error: braces are not closed')
|
||||
return checker.checker_error('EOF error: braces are not closed')
|
||||
}
|
||||
|
||||
if val[checker.checker_idx] == `,` {
|
||||
@ -295,18 +408,18 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
checker.checker_idx++
|
||||
}
|
||||
if val[checker.checker_idx] != `"` {
|
||||
return checker.error('Expecting object key after `,`')
|
||||
return checker.checker_error('Expecting object key after `,`')
|
||||
}
|
||||
} else {
|
||||
if val[checker.checker_idx] == `}` {
|
||||
break
|
||||
} else {
|
||||
return checker.error('invalid object value')
|
||||
return checker.checker_error('invalid object value')
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return checker.error('invalid object value')
|
||||
return checker.checker_error('invalid object value')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -316,7 +429,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
if checker_end >= checker.checker_idx + 2 {
|
||||
checker.checker_idx++
|
||||
} else {
|
||||
return checker.error('EOF error: There are not enough length for an array')
|
||||
return checker.checker_error('EOF error: There are not enough length for an array')
|
||||
}
|
||||
|
||||
for val[checker.checker_idx] != `]` {
|
||||
@ -333,7 +446,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
}
|
||||
|
||||
if checker.checker_idx >= checker_end - 1 {
|
||||
return checker.error('EOF error: array not closed')
|
||||
return checker.checker_error('EOF error: array not closed')
|
||||
}
|
||||
|
||||
checker.check_json_format(val)!
|
||||
@ -346,7 +459,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
break
|
||||
}
|
||||
if checker.checker_idx >= checker_end - 1 {
|
||||
return checker.error('EOF error: braces are not closed')
|
||||
return checker.checker_error('EOF error: braces are not closed')
|
||||
}
|
||||
|
||||
if val[checker.checker_idx] == `,` {
|
||||
@ -355,14 +468,14 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
checker.checker_idx++
|
||||
}
|
||||
if val[checker.checker_idx] == `]` {
|
||||
return checker.error('Cannot use `,`, before `]`')
|
||||
return checker.checker_error('Cannot use `,`, before `]`')
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
if val[checker.checker_idx] == `]` {
|
||||
break
|
||||
} else {
|
||||
return checker.error('`]` after value')
|
||||
return checker.checker_error('`]` after value')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -371,7 +484,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
// check if the JSON string is a valid string
|
||||
|
||||
if checker.checker_idx >= checker_end - 1 {
|
||||
return checker.error('EOF error: string not closed')
|
||||
return checker.checker_error('EOF error: string not closed')
|
||||
}
|
||||
|
||||
checker.checker_idx++
|
||||
@ -380,7 +493,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
for val[checker.checker_idx] != `"` {
|
||||
if val[checker.checker_idx] == `\\` {
|
||||
if checker.checker_idx + 1 >= checker_end - 1 {
|
||||
return checker.error('invalid escape sequence')
|
||||
return checker.checker_error('invalid escape sequence')
|
||||
}
|
||||
escaped_char := val[checker.checker_idx + 1]
|
||||
match escaped_char {
|
||||
@ -401,18 +514,17 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
checker.checker_idx++
|
||||
}
|
||||
else {
|
||||
return checker.error('invalid unicode escape sequence')
|
||||
return checker.checker_error('invalid unicode escape sequence')
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
return checker.error('short unicode escape sequence ${checker.json[checker.checker_idx..
|
||||
escaped_char_last_index + 1]}')
|
||||
return checker.checker_error('short unicode escape sequence ${checker.json[checker.checker_idx..escaped_char_last_index]}')
|
||||
}
|
||||
}
|
||||
else {
|
||||
return checker.error('unknown escape sequence')
|
||||
return checker.checker_error('unknown escape sequence')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -421,50 +533,86 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
}
|
||||
.number {
|
||||
// check if the JSON string is a valid float or integer
|
||||
mut is_negative := val[0] == `-`
|
||||
mut has_dot := false
|
||||
|
||||
mut digits_count := 1
|
||||
|
||||
if is_negative {
|
||||
if val[0] == `-` {
|
||||
checker.checker_idx++
|
||||
}
|
||||
|
||||
for checker.checker_idx < checker_end - 1
|
||||
&& val[checker.checker_idx + 1] !in [`,`, `}`, `]`, ` `, `\t`, `\n`]
|
||||
&& checker.checker_idx < checker_end - 1 {
|
||||
if val[checker.checker_idx] == `.` {
|
||||
if has_dot {
|
||||
return checker.error('invalid float. Multiple dots')
|
||||
}
|
||||
has_dot = true
|
||||
if checker.checker_idx == checker_end {
|
||||
checker.checker_idx--
|
||||
return checker.checker_error('expected digit got EOF')
|
||||
}
|
||||
|
||||
// integer part
|
||||
if val[checker.checker_idx] == `0` {
|
||||
checker.checker_idx++
|
||||
} else if val[checker.checker_idx] >= `1` && val[checker.checker_idx] <= `9` {
|
||||
checker.checker_idx++
|
||||
|
||||
for checker.checker_idx < checker_end && val[checker.checker_idx] >= `0`
|
||||
&& val[checker.checker_idx] <= `9` {
|
||||
checker.checker_idx++
|
||||
continue
|
||||
} else if val[checker.checker_idx] == `-` {
|
||||
if is_negative {
|
||||
return checker.error('invalid float. Multiple negative signs')
|
||||
}
|
||||
} else {
|
||||
return checker.checker_error('expected digit got ${val[checker.checker_idx].ascii_str()}')
|
||||
}
|
||||
|
||||
// fraction part
|
||||
if checker.checker_idx != checker_end && val[checker.checker_idx] == `.` {
|
||||
checker.checker_idx++
|
||||
|
||||
if checker.checker_idx == checker_end {
|
||||
checker.checker_idx--
|
||||
return checker.checker_error('expected digit got EOF')
|
||||
}
|
||||
|
||||
if val[checker.checker_idx] >= `0` && val[checker.checker_idx] <= `9` {
|
||||
for checker.checker_idx < checker_end && val[checker.checker_idx] >= `0`
|
||||
&& val[checker.checker_idx] <= `9` {
|
||||
checker.checker_idx++
|
||||
}
|
||||
checker.checker_idx++
|
||||
continue
|
||||
} else {
|
||||
if val[checker.checker_idx] < `0` || val[checker.checker_idx] > `9` {
|
||||
return checker.error('invalid number')
|
||||
return checker.checker_error('expected digit got ${val[checker.checker_idx].ascii_str()}')
|
||||
}
|
||||
}
|
||||
|
||||
// exponent part
|
||||
if checker.checker_idx != checker_end
|
||||
&& (val[checker.checker_idx] == `e` || val[checker.checker_idx] == `E`) {
|
||||
checker.checker_idx++
|
||||
|
||||
if checker.checker_idx == checker_end {
|
||||
checker.checker_idx--
|
||||
return checker.checker_error('expected digit got EOF')
|
||||
}
|
||||
|
||||
if val[checker.checker_idx] == `-` || val[checker.checker_idx] == `+` {
|
||||
checker.checker_idx++
|
||||
|
||||
if checker.checker_idx == checker_end {
|
||||
checker.checker_idx--
|
||||
return checker.checker_error('expected digit got EOF')
|
||||
}
|
||||
}
|
||||
|
||||
if digits_count >= 64 {
|
||||
return checker.error('number exceeds 64 digits')
|
||||
if val[checker.checker_idx] >= `0` && val[checker.checker_idx] <= `9` {
|
||||
for checker.checker_idx < checker_end && val[checker.checker_idx] >= `0`
|
||||
&& val[checker.checker_idx] <= `9` {
|
||||
checker.checker_idx++
|
||||
}
|
||||
} else {
|
||||
return checker.checker_error('expected digit got ${val[checker.checker_idx].ascii_str()}')
|
||||
}
|
||||
digits_count++
|
||||
checker.checker_idx++
|
||||
}
|
||||
|
||||
checker.checker_idx--
|
||||
}
|
||||
.boolean {
|
||||
// check if the JSON string is a valid boolean
|
||||
match val[checker.checker_idx] {
|
||||
`t` {
|
||||
if checker_end - checker.checker_idx <= 3 {
|
||||
return checker.error('EOF error: expecting `true`')
|
||||
return checker.checker_error('EOF error: expecting `true`')
|
||||
}
|
||||
|
||||
is_not_ok := unsafe {
|
||||
@ -473,14 +621,14 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
}
|
||||
|
||||
if is_not_ok != 0 {
|
||||
return checker.error('invalid boolean value. Got `${checker.json[checker.checker_idx..
|
||||
return checker.checker_error('invalid boolean value. Got `${checker.json[checker.checker_idx..
|
||||
checker.checker_idx + 4]}` instead of `true`')
|
||||
}
|
||||
checker.checker_idx += 3
|
||||
}
|
||||
`f` {
|
||||
if checker_end - checker.checker_idx <= 4 {
|
||||
return checker.error('EOF error: expecting `false`')
|
||||
return checker.checker_error('EOF error: expecting `false`')
|
||||
}
|
||||
|
||||
is_not_ok := unsafe {
|
||||
@ -489,14 +637,14 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
}
|
||||
|
||||
if is_not_ok != 0 {
|
||||
return checker.error('invalid boolean value. Got `${checker.json[checker.checker_idx..
|
||||
return checker.checker_error('invalid boolean value. Got `${checker.json[checker.checker_idx..
|
||||
checker.checker_idx + 5]}` instead of `false`')
|
||||
}
|
||||
|
||||
checker.checker_idx += 4
|
||||
}
|
||||
else {
|
||||
return checker.error('invalid boolean')
|
||||
return checker.checker_error('invalid boolean')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -511,7 +659,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
for checker.checker_idx < checker_end - 1 && val[checker.checker_idx] !in [`,`, `:`, `}`, `]`] {
|
||||
// get trash characters after the value
|
||||
if val[checker.checker_idx] !in whitespace_chars {
|
||||
checker.error('invalid value. Unexpected character after ${value_kind} end')!
|
||||
checker.checker_error('invalid value. Unexpected character after ${value_kind} end')!
|
||||
} else {
|
||||
// whitespace
|
||||
}
|
||||
@ -519,11 +667,33 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
}
|
||||
}
|
||||
|
||||
// get_value_kind returns the kind of a JSON value.
|
||||
fn get_value_kind(value u8) ValueKind {
|
||||
if value == u8(`"`) {
|
||||
return .string_
|
||||
} else if value == u8(`t`) || value == u8(`f`) {
|
||||
return .boolean
|
||||
} else if value == u8(`{`) {
|
||||
return .object
|
||||
} else if value == u8(`[`) {
|
||||
return .array
|
||||
} else if (value >= u8(48) && value <= u8(57)) || value == u8(`-`) {
|
||||
return .number
|
||||
} else if value == u8(`n`) {
|
||||
return .null
|
||||
}
|
||||
return .unknown
|
||||
}
|
||||
|
||||
// decode decodes a JSON string into a specified type.
|
||||
@[manualfree]
|
||||
pub fn decode[T](val string) !T {
|
||||
if val == '' {
|
||||
return error('empty string')
|
||||
return JsonDecodeError{
|
||||
message: 'empty string'
|
||||
line: 1
|
||||
character: 1
|
||||
}
|
||||
}
|
||||
mut decoder := Decoder{
|
||||
json: val
|
||||
@ -611,7 +781,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
||||
|
||||
val = string_buffer.bytestr()
|
||||
} else {
|
||||
return error('Expected string, but got ${string_info.value_kind}')
|
||||
return decoder.decode_error('Expected string, but got ${string_info.value_kind}')
|
||||
}
|
||||
} $else $if T.unaliased_typ is $sumtype {
|
||||
decoder.decode_sumtype(mut val)!
|
||||
@ -651,7 +821,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
||||
for attr in field.attrs {
|
||||
if attr.starts_with('json:') {
|
||||
if attr.len <= 6 {
|
||||
return error('`json` attribute must have an argument')
|
||||
return decoder.decode_error('`json` attribute must have an argument')
|
||||
}
|
||||
json_name_str = unsafe { attr.str + 6 }
|
||||
json_name_len = attr.len - 6
|
||||
@ -756,7 +926,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
||||
|
||||
if current_field_info.value.is_skip {
|
||||
if current_field_info.value.is_required == false {
|
||||
return error('This should not happen. Please, file a bug. `skip` field should not be processed here without a `required` attribute')
|
||||
return decoder.decode_error('This should not happen. Please, file a bug. `skip` field should not be processed here without a `required` attribute')
|
||||
}
|
||||
current_field_info.value.decoded_with_value_info_node = decoder.current_node
|
||||
break
|
||||
@ -765,7 +935,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
||||
if current_field_info.value.is_raw {
|
||||
$if field.unaliased_typ is $enum {
|
||||
// workaround to avoid the error: enums can only be assigned `int` values
|
||||
return error('`raw` attribute cannot be used with enum fields')
|
||||
return decoder.decode_error('`raw` attribute cannot be used with enum fields')
|
||||
} $else $if field.typ is ?string {
|
||||
position := decoder.current_node.value.position
|
||||
end := position + decoder.current_node.value.length
|
||||
@ -797,7 +967,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
||||
decoder.current_node = decoder.current_node.next
|
||||
}
|
||||
} $else {
|
||||
return error('`raw` attribute can only be used with string fields')
|
||||
return decoder.decode_error('`raw` attribute can only be used with string fields')
|
||||
}
|
||||
} else {
|
||||
$if field.typ is $option {
|
||||
@ -840,14 +1010,14 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
||||
continue
|
||||
}
|
||||
if current_field_info.value.decoded_with_value_info_node == unsafe { nil } {
|
||||
return error('missing required field `${unsafe {
|
||||
return decoder.decode_error('missing required field `${unsafe {
|
||||
tos(current_field_info.value.field_name_str, current_field_info.value.field_name_len)
|
||||
}}`')
|
||||
}
|
||||
current_field_info = current_field_info.next
|
||||
}
|
||||
} else {
|
||||
return error('Expected object, but got ${struct_info.value_kind}')
|
||||
return decoder.decode_error('Expected object, but got ${struct_info.value_kind}')
|
||||
}
|
||||
unsafe {
|
||||
struct_fields_info.free()
|
||||
@ -857,27 +1027,23 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
||||
value_info := decoder.current_node.value
|
||||
|
||||
if value_info.value_kind != .boolean {
|
||||
return error('Expected boolean, but got ${value_info.value_kind}')
|
||||
return decoder.decode_error('Expected boolean, but got ${value_info.value_kind}')
|
||||
}
|
||||
|
||||
unsafe {
|
||||
val = vmemcmp(decoder.json.str + value_info.position, true_in_string.str,
|
||||
true_in_string.len) == 0
|
||||
}
|
||||
} $else $if T.unaliased_typ in [$float, $int, $enum] {
|
||||
} $else $if T.unaliased_typ is $float || T.unaliased_typ is $int || T.unaliased_typ is $enum {
|
||||
value_info := decoder.current_node.value
|
||||
|
||||
if value_info.value_kind == .number {
|
||||
bytes := unsafe { (decoder.json.str + value_info.position).vbytes(value_info.length) }
|
||||
|
||||
unsafe {
|
||||
string_buffer_to_generic_number(val, bytes)
|
||||
}
|
||||
unsafe { decoder.decode_number(&val)! }
|
||||
} else {
|
||||
return error('Expected number, but got ${value_info.value_kind}')
|
||||
return decoder.decode_error('Expected number, but got ${value_info.value_kind}')
|
||||
}
|
||||
} $else {
|
||||
return error('cannot decode value with ${typeof(val).name} type')
|
||||
return decoder.decode_error('cannot decode value with ${typeof(val).name} type')
|
||||
}
|
||||
|
||||
if decoder.current_node != unsafe { nil } {
|
||||
@ -907,7 +1073,7 @@ fn (mut decoder Decoder) decode_array[T](mut val []T) ! {
|
||||
val << array_element
|
||||
}
|
||||
} else {
|
||||
return error('Expected array, but got ${array_info.value_kind}')
|
||||
return decoder.decode_error('Expected array, but got ${array_info.value_kind}')
|
||||
}
|
||||
}
|
||||
|
||||
@ -951,101 +1117,176 @@ fn (mut decoder Decoder) decode_map[K, V](mut val map[K]V) ! {
|
||||
decoder.decode_value(mut val[key])!
|
||||
}
|
||||
} else {
|
||||
return error('Expected object, but got ${map_info.value_kind}')
|
||||
return decoder.decode_error('Expected object, but got ${map_info.value_kind}')
|
||||
}
|
||||
}
|
||||
|
||||
// get_value_kind returns the kind of a JSON value.
|
||||
fn get_value_kind(value u8) ValueKind {
|
||||
if value == u8(`"`) {
|
||||
return .string_
|
||||
} else if value == u8(`t`) || value == u8(`f`) {
|
||||
return .boolean
|
||||
} else if value == u8(`{`) {
|
||||
return .object
|
||||
} else if value == u8(`[`) {
|
||||
return .array
|
||||
} else if (value >= u8(48) && value <= u8(57)) || value == u8(`-`) {
|
||||
return .number
|
||||
} else if value == u8(`n`) {
|
||||
return .null
|
||||
}
|
||||
return .unknown
|
||||
}
|
||||
|
||||
fn create_value_from_optional[T](val ?T) ?T {
|
||||
return T{}
|
||||
}
|
||||
|
||||
// string_buffer_to_generic_number converts a buffer of bytes (data) into a generic type T and
|
||||
// stores the result in the provided result pointer.
|
||||
// The function supports conversion to the following types:
|
||||
// - Signed integers: i8, i16, i32, i64
|
||||
// - Unsigned integers: u8, u16, u32, u64
|
||||
// - Floating-point numbers: f32, f64
|
||||
//
|
||||
// For signed integers, the function handles negative numbers by checking for a '-' character.
|
||||
// For floating-point numbers, the function handles decimal points and adjusts the result
|
||||
// accordingly.
|
||||
//
|
||||
// If the type T is not supported, the function will panic with an appropriate error message.
|
||||
//
|
||||
// Parameters:
|
||||
// - data []u8: The buffer of bytes to be converted.
|
||||
// - result &T: A pointer to the variable where the converted result will be stored.
|
||||
//
|
||||
// NOTE: This aims works with not new memory allocated data, to more efficient use `vbytes` before
|
||||
@[direct_array_access; unsafe]
|
||||
pub fn string_buffer_to_generic_number[T](result &T, data []u8) {
|
||||
$if T.unaliased_typ is $int {
|
||||
mut is_negative := false
|
||||
for ch in data {
|
||||
if ch == `-` {
|
||||
is_negative = true
|
||||
continue
|
||||
}
|
||||
digit := T(ch - `0`)
|
||||
*result = T(*result * 10 + digit)
|
||||
}
|
||||
if is_negative {
|
||||
*result *= -1
|
||||
}
|
||||
} $else $if T.unaliased_typ is $float {
|
||||
mut is_negative := false
|
||||
mut decimal_seen := false
|
||||
mut decimal_divider := T(1)
|
||||
fn get_number_max[T](num T) T {
|
||||
$if num is i8 {
|
||||
return max_i8
|
||||
} $else $if num is i16 {
|
||||
return max_i16
|
||||
} $else $if num is i32 {
|
||||
return max_i32
|
||||
} $else $if num is i64 {
|
||||
return max_i64
|
||||
} $else $if num is u8 {
|
||||
return max_u8
|
||||
} $else $if num is u16 {
|
||||
return max_u16
|
||||
} $else $if num is u32 {
|
||||
return max_u32
|
||||
} $else $if num is u64 {
|
||||
return max_u64
|
||||
} $else $if num is int {
|
||||
return max_int
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
for ch in data {
|
||||
if ch == `-` {
|
||||
is_negative = true
|
||||
continue
|
||||
}
|
||||
if ch == `.` {
|
||||
decimal_seen = true
|
||||
continue
|
||||
}
|
||||
fn get_number_min[T](num T) T {
|
||||
$if num is i8 {
|
||||
return min_i8
|
||||
} $else $if num is i16 {
|
||||
return min_i16
|
||||
} $else $if num is i32 {
|
||||
return min_i32
|
||||
} $else $if num is i64 {
|
||||
return min_i64
|
||||
} $else $if num is u8 {
|
||||
return min_u8
|
||||
} $else $if num is u16 {
|
||||
return min_u16
|
||||
} $else $if num is u32 {
|
||||
return min_u32
|
||||
} $else $if num is u64 {
|
||||
return min_u64
|
||||
} $else $if num is int {
|
||||
return min_int
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
digit := T(ch - u8(`0`))
|
||||
|
||||
if decimal_seen {
|
||||
decimal_divider *= 10
|
||||
*result += T(digit / decimal_divider)
|
||||
} else {
|
||||
*result = T(*result * 10 + digit)
|
||||
}
|
||||
}
|
||||
if is_negative {
|
||||
*result *= -1
|
||||
}
|
||||
} $else $if T.unaliased_typ is $enum {
|
||||
// Convert the string to an integer
|
||||
enumeration := 0
|
||||
for ch in data {
|
||||
digit := int(ch - `0`)
|
||||
enumeration = enumeration * 10 + digit
|
||||
}
|
||||
*result = T(enumeration)
|
||||
fn get_number_digits[T](num T) int {
|
||||
return $if T.unaliased_typ is i8 || T.unaliased_typ is u8 {
|
||||
3
|
||||
} $else $if T.unaliased_typ is i16 || T.unaliased_typ is u16 {
|
||||
5
|
||||
} $else $if T.unaliased_typ is i32 || T.unaliased_typ is u32 || T.unaliased_typ is int {
|
||||
10
|
||||
} $else $if T.unaliased_typ is i64 {
|
||||
19
|
||||
} $else $if T.unaliased_typ is u64 {
|
||||
20
|
||||
} $else {
|
||||
panic('unsupported type ' + typeof[T]().name)
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// use pointer instead of mut so enum cast works
|
||||
@[unsafe]
|
||||
fn (mut decoder Decoder) decode_number[T](val &T) ! {
|
||||
number_info := decoder.current_node.value
|
||||
|
||||
$if T.unaliased_typ is $float {
|
||||
*val = T(strconv.atof_quick(decoder.json[number_info.position..number_info.position +
|
||||
number_info.length]))
|
||||
} $else $if T.unaliased_typ is $enum {
|
||||
mut result := 0
|
||||
decoder.decode_number(&result)!
|
||||
*val = T(result)
|
||||
} $else { // this part is a minefield
|
||||
mut is_negative := false
|
||||
mut index := 0
|
||||
|
||||
if decoder.json[number_info.position] == `-` {
|
||||
$if T.unaliased_typ is u8 || T.unaliased_typ is u16 || T.unaliased_typ is u32
|
||||
|| T.unaliased_typ is u64 || T.unaliased_typ is $enum {
|
||||
decoder.decode_error('expected positive integer for ${typeof(val).name} but got ${decoder.json[number_info.position..
|
||||
number_info.position + number_info.length]}')!
|
||||
}
|
||||
|
||||
is_negative = true
|
||||
index++
|
||||
}
|
||||
|
||||
// doing it like this means the minimum of signed numbers does not overflow before being inverted
|
||||
if !is_negative {
|
||||
digit_amount := get_number_digits(*val)
|
||||
|
||||
if number_info.length > digit_amount {
|
||||
decoder.decode_error('overflows ${typeof(val).name}')!
|
||||
}
|
||||
|
||||
for index < int_min(number_info.length, digit_amount - 1) {
|
||||
digit := T(decoder.json[number_info.position + index] - `0`)
|
||||
|
||||
if digit > 9 { // comma, e and E are all smaller 0 in ASCII so they underflow
|
||||
decoder.decode_error('expected integer but got real number')!
|
||||
}
|
||||
|
||||
*val = *val * 10 + digit
|
||||
|
||||
index++
|
||||
}
|
||||
|
||||
if index == digit_amount - 1 {
|
||||
digit := T(decoder.json[number_info.position + index] - `0`)
|
||||
|
||||
if digit > 9 { // comma, e and E are all smaller 0 in ASCII so they underflow
|
||||
decoder.decode_error('expected integer but got real number')!
|
||||
}
|
||||
|
||||
type_max := get_number_max(*val)
|
||||
max_digits := type_max / 10
|
||||
last_digit := type_max % 10
|
||||
|
||||
if *val > max_digits || (*val == max_digits && digit > last_digit) {
|
||||
decoder.decode_error('overflows ${typeof(val).name}s')!
|
||||
}
|
||||
|
||||
*val = *val * 10 + digit
|
||||
}
|
||||
} else {
|
||||
digit_amount := get_number_digits(*val) + 1
|
||||
|
||||
if number_info.length > digit_amount {
|
||||
decoder.decode_error('underflows ${typeof(val).name}')!
|
||||
}
|
||||
|
||||
for index < int_min(number_info.length, digit_amount - 1) {
|
||||
digit := T(decoder.json[number_info.position + index] - `0`)
|
||||
|
||||
if digit > 9 { // comma, e and E are all smaller 0 in ASCII so they underflow
|
||||
decoder.decode_error('expected integer but got real number')!
|
||||
}
|
||||
|
||||
*val = *val * 10 - digit
|
||||
|
||||
index++
|
||||
}
|
||||
|
||||
if index == digit_amount - 1 {
|
||||
digit := T(decoder.json[number_info.position + index] - `0`)
|
||||
|
||||
if digit > 9 { // comma, e and E are all smaller 0 in ASCII so they underflow
|
||||
decoder.decode_error('expected integer but got real number')!
|
||||
}
|
||||
|
||||
type_min := get_number_min(*val)
|
||||
min_digits := type_min / 10
|
||||
last_digit := type_min % 10
|
||||
|
||||
if *val < min_digits || (*val == min_digits && -digit < last_digit) {
|
||||
decoder.decode_error('underflows ${typeof(val).name}')!
|
||||
}
|
||||
|
||||
*val = *val * 10 - digit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,13 +19,13 @@ fn (mut decoder Decoder) get_decoded_sumtype_workaround[T](initialized_sumtype T
|
||||
decoder.current_node = decoder.current_node.next
|
||||
return T(initialized_sumtype)
|
||||
} else {
|
||||
decoder.error('sumtype option only support decoding null->none (for now)')!
|
||||
decoder.decode_error('sumtype option only support decoding null->none (for now)')!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
decoder.error('could not decode resolved sumtype (should not happen)')!
|
||||
decoder.decode_error('could not decode resolved sumtype (should not happen)')!
|
||||
return initialized_sumtype // suppress compiler error
|
||||
}
|
||||
|
||||
@ -288,15 +288,15 @@ fn (mut decoder Decoder) init_sumtype_by_value_kind[T](mut val T, value_info Val
|
||||
}
|
||||
|
||||
if failed_struct {
|
||||
decoder.error('could not resolve sumtype `${T.name}`, missing "_type" field?')!
|
||||
decoder.decode_error('could not resolve sumtype `${T.name}`, missing "_type" field?')!
|
||||
}
|
||||
|
||||
decoder.error('could not resolve sumtype `${T.name}`, got ${value_info.value_kind}.')!
|
||||
decoder.decode_error('could not resolve sumtype `${T.name}`, got ${value_info.value_kind}.')!
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) decode_sumtype[T](mut val T) ! {
|
||||
$if T is $alias {
|
||||
decoder.error('Type aliased sumtypes not supported.')!
|
||||
decoder.decode_error('Type aliased sumtypes not supported.')!
|
||||
} $else {
|
||||
value_info := decoder.current_node.value
|
||||
|
||||
|
@ -5,42 +5,66 @@ fn test_check_if_json_match() {
|
||||
mut has_error := false
|
||||
|
||||
decode[string]('{"key": "value"}') or {
|
||||
assert err.str() == 'Expected string, but got object'
|
||||
if err is JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected string, but got object'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error'
|
||||
has_error = false
|
||||
|
||||
decode[map[string]string]('"value"') or {
|
||||
assert err.str() == 'Expected object, but got string_'
|
||||
if err is JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected object, but got string_'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error'
|
||||
has_error = false
|
||||
|
||||
decode[[]int]('{"key": "value"}') or {
|
||||
assert err.str() == 'Expected array, but got object'
|
||||
if err is JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected array, but got object'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error'
|
||||
has_error = false
|
||||
|
||||
decode[string]('[1, 2, 3]') or {
|
||||
assert err.str() == 'Expected string, but got array'
|
||||
if err is JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected string, but got array'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error'
|
||||
has_error = false
|
||||
|
||||
decode[int]('{"key": "value"}') or {
|
||||
assert err.str() == 'Expected number, but got object'
|
||||
if err is JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected number, but got object'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error'
|
||||
has_error = false
|
||||
|
||||
decode[bool]('{"key": "value"}') or {
|
||||
assert err.str() == 'Expected boolean, but got object'
|
||||
if err is JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected boolean, but got object'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error'
|
||||
@ -125,43 +149,43 @@ fn test_check_json_format() {
|
||||
json_and_error_message := [
|
||||
{
|
||||
'json': ']'
|
||||
'error': '\n]\n^ unknown value kind'
|
||||
'error': 'Syntax: unknown value kind'
|
||||
},
|
||||
{
|
||||
'json': '}'
|
||||
'error': '\n}\n^ unknown value kind'
|
||||
'error': 'Syntax: unknown value kind'
|
||||
},
|
||||
{
|
||||
'json': 'truely'
|
||||
'error': '\ntruel\n ^ invalid value. Unexpected character after boolean end'
|
||||
'error': 'Syntax: invalid value. Unexpected character after boolean end'
|
||||
},
|
||||
{
|
||||
'json': '0[1]' //
|
||||
'error': '\n0[\n ^ invalid number'
|
||||
'json': '0[1]'
|
||||
'error': 'Syntax: invalid value. Unexpected character after number end'
|
||||
},
|
||||
{
|
||||
'json': '[1, 2, g3]'
|
||||
'error': '\n[1, 2, g\n ^ unknown value kind'
|
||||
'error': 'Syntax: unknown value kind'
|
||||
},
|
||||
{
|
||||
'json': '[1, 2,, 3]'
|
||||
'error': '\n[1, 2,,\n ^ unknown value kind'
|
||||
'error': 'Syntax: unknown value kind'
|
||||
},
|
||||
{
|
||||
'json': '{"key": 123'
|
||||
'error': '\n{"key": 123\n ^ EOF error: braces are not closed'
|
||||
'error': 'Syntax: EOF error: braces are not closed'
|
||||
},
|
||||
{
|
||||
'json': '{"key": 123,'
|
||||
'error': '\n{"key": 123,\n ^ EOF error: braces are not closed'
|
||||
'error': 'Syntax: EOF error: braces are not closed'
|
||||
},
|
||||
{
|
||||
'json': '{"key": 123, "key2": 456,}'
|
||||
'error': '\n{"key": 123, "key2": 456,}\n ^ Expecting object key after `,`'
|
||||
'error': 'Syntax: Expecting object key after `,`'
|
||||
},
|
||||
{
|
||||
'json': '[[1, 2, 3], [4, 5, 6],]'
|
||||
'error': '\n[[1, 2, 3], [4, 5, 6],]\n ^ Cannot use `,`, before `]`'
|
||||
'error': 'Syntax: Cannot use `,`, before `]`'
|
||||
},
|
||||
]
|
||||
|
||||
@ -173,7 +197,9 @@ fn test_check_json_format() {
|
||||
}
|
||||
|
||||
checker.check_json_format(json_and_error['json']) or {
|
||||
assert err.str() == json_and_error['error']
|
||||
if err is JsonDecodeError {
|
||||
assert err.message == json_and_error['error']
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error ${json_and_error['error']}'
|
||||
|
@ -6,17 +6,11 @@ fn test_number() {
|
||||
assert json.decode[u8]('1')! == 1
|
||||
assert json.decode[u8]('201')! == 201
|
||||
|
||||
assert json.decode[u8]('-1')! == u8(-1)
|
||||
assert json.decode[u8]('-127')! == u8(-127)
|
||||
|
||||
// Test u16
|
||||
assert json.decode[u16]('0')! == 0
|
||||
assert json.decode[u16]('1')! == 1
|
||||
assert json.decode[u16]('201')! == 201
|
||||
|
||||
assert json.decode[u16]('-1')! == u16(-1)
|
||||
assert json.decode[u16]('-201')! == u16(-201)
|
||||
|
||||
// Test u32
|
||||
assert json.decode[u32]('0')! == 0
|
||||
assert json.decode[u32]('1')! == 1
|
||||
@ -90,6 +84,38 @@ fn test_number() {
|
||||
|
||||
assert json.decode[f64]('1234567890')! == 1234567890.0
|
||||
assert json.decode[f64]('-1234567890')! == -1234567890.0
|
||||
|
||||
assert json.decode[f64]('1e10')! == 10000000000
|
||||
assert json.decode[f64]('1E10')! == 10000000000
|
||||
assert json.decode[f64]('1e+10')! == 10000000000
|
||||
assert json.decode[f64]('1e-10')! == 0.0000000001
|
||||
assert json.decode[f64]('-1e10')! == -10000000000
|
||||
assert json.decode[f64]('-1E-10')! == -0.0000000001
|
||||
assert json.decode[f64]('0.123e3')! - 123 < 0.0000001
|
||||
assert json.decode[f64]('10.5E+3')! == 10500
|
||||
|
||||
// Test Over/Underflow
|
||||
assert json.decode[i8]('127')! == 127
|
||||
assert json.decode[i8]('-128')! == -128
|
||||
|
||||
if x := json.decode[i8]('128') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[i8]('130') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[i8]('1000') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[i8]('-129') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[i8]('-130') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[i8]('-1000') {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
fn test_boolean() {
|
||||
|
@ -34,7 +34,11 @@ fn test_json_string_invalid_escapes() {
|
||||
mut has_error := false
|
||||
|
||||
json.decode[string](r'"\x"') or {
|
||||
assert err.msg() == '\n"\\\n ^ unknown escape sequence'
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 2
|
||||
assert err.message == 'Syntax: unknown escape sequence'
|
||||
}
|
||||
has_error = true
|
||||
} // Invalid escape
|
||||
|
||||
@ -42,7 +46,11 @@ fn test_json_string_invalid_escapes() {
|
||||
has_error = false
|
||||
|
||||
json.decode[string](r'"\u123"') or {
|
||||
assert err.msg() == '\n"\\\n ^ short unicode escape sequence \\u123"'
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 2
|
||||
assert err.message == 'Syntax: short unicode escape sequence \\u123'
|
||||
}
|
||||
has_error = true
|
||||
} // Incomplete Unicode
|
||||
|
||||
|
@ -57,7 +57,11 @@ fn test_nested_array_object() {
|
||||
|
||||
fn test_raw_decode_map_invalid() {
|
||||
json.decode[json2.Any]('{"name","Bob","age":20}') or {
|
||||
assert err.msg() == '\n{"name",\n ^ invalid value after object key'
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 8
|
||||
assert err.message == 'Syntax: invalid value after object key'
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@ -66,7 +70,11 @@ fn test_raw_decode_map_invalid() {
|
||||
|
||||
fn test_raw_decode_array_invalid() {
|
||||
json.decode[json2.Any]('["Foo", 1,}') or {
|
||||
assert err.msg() == '\n["Foo", 1,}\n ^ EOF error: array not closed'
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 11
|
||||
assert err.message == 'Syntax: EOF error: array not closed'
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -43,7 +43,11 @@ struct DbConfig {
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_empty() {
|
||||
json.decode[DbConfig]('') or {
|
||||
assert err.msg() == 'empty string'
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'empty string'
|
||||
}
|
||||
return
|
||||
}
|
||||
assert false
|
||||
@ -51,9 +55,11 @@ fn test_decode_error_message_should_have_enough_context_empty() {
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_just_brace() {
|
||||
json.decode[DbConfig]('{') or {
|
||||
assert err.msg() == '
|
||||
{
|
||||
^ EOF error: expecting a complete object after `{`'
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Syntax: EOF error: expecting a complete object after `{`'
|
||||
}
|
||||
return
|
||||
}
|
||||
assert false
|
||||
@ -67,7 +73,11 @@ fn test_decode_error_message_should_have_enough_context_trailing_comma_at_end()
|
||||
}'
|
||||
|
||||
json.decode[DbConfig](txt) or {
|
||||
assert err.msg() == '\n\n}\n ^ Expecting object key after `,`'
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 5
|
||||
assert err.character == 1
|
||||
assert err.message == 'Syntax: Expecting object key after `,`'
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@ -77,7 +87,11 @@ fn test_decode_error_message_should_have_enough_context_trailing_comma_at_end()
|
||||
fn test_decode_error_message_should_have_enough_context_in_the_middle() {
|
||||
txt := '{"host": "localhost", "dbname": "alex" "user": "alex", "port": "1234"}'
|
||||
json.decode[DbConfig](txt) or {
|
||||
assert err.msg() == '\n{"host": "localhost", "dbname": "alex" "\n ^ invalid value. Unexpected character after string_ end'
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 40
|
||||
assert err.message == 'Syntax: invalid value. Unexpected character after string_ end'
|
||||
}
|
||||
return
|
||||
}
|
||||
assert false
|
||||
|
Loading…
x
Reference in New Issue
Block a user