Improved error handling and pretty-printing for arrays

This commit is contained in:
Zahary Karadjov 2019-03-20 01:54:03 +02:00
parent 002c6bbb38
commit c2daa5a23c
5 changed files with 109 additions and 22 deletions

View File

@ -23,7 +23,7 @@ type
tkColon,
tkComma
JsonError* = enum
JsonErrorKind* = enum
errNone = "no error",
errHexCharExpected = "hex char expected (part of escape sequence)",
errStringExpected = "string expected",
@ -48,7 +48,7 @@ type
tokenStart: int
tok*: TokKind
err*: JsonError
err*: JsonErrorKind
intVal*: int64
floatVal*: float
@ -91,7 +91,7 @@ proc init*(T: type JsonLexer, stream: ref ByteStream, mode = defaultJsonMode): a
type AS = ref AsciiStream
init(JsonLexer, AS(stream), mode)
template error(error: JsonError) {.dirty.} =
template error(error: JsonErrorKind) {.dirty.} =
lexer.err = error
lexer.tok = tkError
return

View File

@ -1,22 +1,26 @@
import
strutils, typetraits, macros,
faststreams/input_stream, serialization/object_serialization,
strutils, typetraits, macros, strformat,
faststreams/input_stream, serialization/[object_serialization, errors],
types, lexer
export
types
types, errors
type
JsonReader* = object
lexer: JsonLexer
JsonReaderError* = object of CatchableError
JsonReaderError* = object of JsonError
line*, col*: int
UnexpectedField* = object of JsonReaderError
encounteredField*: cstring
deserializedType*: cstring
CustomSerializationError* = object of JsonReaderError
deserializedType*: cstring
innerException*: ref CatchableError
ExpectedTokenCategory* = enum
etBool = "bool literal"
etInt = "integer"
@ -33,16 +37,21 @@ type
encountedToken*: TokKind
expectedToken*: ExpectedTokenCategory
proc init*(T: type JsonReader, stream: AsciiStreamVar, mode = defaultJsonMode): T =
result.lexer = JsonLexer.init(stream, mode)
result.lexer.next()
method formatMsg*(err: ref JsonReaderError, filename: string): string =
fmt"{filename}({err.line}, {err.col}) Error while reading json file"
method formatMsg*(err: ref UnexpectedField, filename: string): string =
fmt"{filename}({err.line}, {err.col}) Unexpected field '{err.encounteredField}' while deserialing {err.deserializedType}"
method formatMsg*(err: ref UnexpectedToken, filename: string): string =
fmt"{filename}({err.line}, {err.col}) Unexpected token '{err.encountedToken}' in place of '{err.expectedToken}'"
method formatMsg*(err: ref CustomSerializationError, filename: string): string =
fmt"{filename}({err.line}, {err.col}) Error while deserializing '{err.deserializedType}': {err.innerException.msg}"
template init*(T: type JsonReader, stream: ByteStreamVar, mode = defaultJsonMode): auto =
init JsonReader, AsciiStreamVar(stream), mode
proc setParsed[T: enum](e: var T, s: string) =
e = parseEnum[T](s)
proc assignLineNumber(ex: ref JsonReaderError, r: JsonReader) =
ex.line = r.lexer.line
ex.col = r.lexer.col
@ -61,6 +70,22 @@ proc raiseUnexpectedField(r: JsonReader, fieldName, deserializedType: cstring) =
ex.deserializedType = deserializedType
raise ex
proc raiseCustomSerializationError(r: JsonReader,
deserializedType: cstring,
innerException: ref CatchableError) =
var ex = new CustomSerializationError
ex.assignLineNumber(r)
ex.deserializedType = deserializedType
ex.innerException = innerException
raise ex
proc init*(T: type JsonReader, stream: AsciiStreamVar, mode = defaultJsonMode): T =
result.lexer = JsonLexer.init(stream, mode)
result.lexer.next()
proc setParsed[T: enum](e: var T, s: string) =
e = parseEnum[T](s)
proc requireToken(r: JsonReader, tk: TokKind) =
if r.lexer.tok != tk:
r.raiseUnexpectedToken case tk
@ -77,7 +102,31 @@ proc skipToken(r: var JsonReader, tk: TokKind) =
r.requireToken tk
r.lexer.next()
proc readImpl(r: var JsonReader, value: var auto) =
template checkedRead(r: var JsonReader, value: var auto) =
# TODO: Here, we are interested in catching errors only from
# user-defined overloads of `readValue`. At the moment, this
# is a bit hard to do, because we cannot discriminate between
# the overload here and the mixed-in ones.
# Perhaps a more cleaner implementation will have two separate
# overloadable procs:
#
# * `readValue`
# intended for people who implement Readers
#
# * `customReadValue`
# intended for user-defined types,
# may be hidden behind a `customSerialization` template
mixin readValue
try:
readValue(r, value)
except JsonError:
raise
except CatchableError as err:
type T = type(value)
raiseCustomSerializationError(r, typetraits.name(T), err)
proc readValue*(r: var JsonReader, value: var auto) =
mixin readValue
let tok = r.lexer.tok
@ -126,7 +175,7 @@ proc readImpl(r: var JsonReader, value: var auto) =
while true:
let lastPos = value.len
value.setLen(lastPos + 1)
readValue(r, value[lastPos])
checkedRead(r, value[lastPos])
if r.lexer.tok != tkComma: break
r.lexer.next()
r.skipToken tkBracketRi
@ -136,9 +185,9 @@ proc readImpl(r: var JsonReader, value: var auto) =
for i in low(value) ..< high(value):
# TODO: dont's ask. this makes the code compile
if false: value[i] = value[i]
readValue(r, value[i])
checkedRead(r, value[i])
r.skipToken tkComma
readValue(r, value[high(value)])
checkedRead(r, value[high(value)])
r.skipToken tkBracketRi
elif value is (object or tuple):
@ -168,6 +217,3 @@ proc readImpl(r: var JsonReader, value: var auto) =
const typeName = typetraits.name(value.type)
{.error: "Failed to convert to JSON an unsupported type: " & typeName.}
template readValue*(r: var JsonReader, value: var auto) =
readImpl(r, value)

View File

@ -1,9 +1,15 @@
import
serialization/errors
export
errors
type
JsonMode* = enum
Relaxed
Portable
JsonError* = object of CatchableError
JsonError* = object of SerializationError
const
defaultJsonMode* = JsonMode.Relaxed

View File

@ -98,10 +98,27 @@ proc writeArray[T](w: var JsonWriter, elements: openarray[T]) =
mixin writeValue
append '['
if w.hasPrettyOutput:
append '\n'
w.nestingLevel += 2
indent()
for i, e in elements:
if i != 0: append ','
if i != 0:
append ','
if w.hasPrettyOutput:
append '\n'
indent()
w.state = RecordExpected
w.writeValue(e)
if w.hasPrettyOutput:
append '\n'
w.nestingLevel -= 2
indent()
append ']'
proc writeImpl(w: var JsonWriter, value: auto) =

View File

@ -22,6 +22,9 @@ type
z: ref Simple
# o: Option[Simple]
HoldsArray = object
data: seq[int]
Invalid = object
distance: Mile
@ -59,6 +62,21 @@ suite "toJson tests":
}
"""
test "arrays are printed correctly":
var x = HoldsArray(data: @[1, 2, 3, 4])
check:
x.toJson(pretty = true) == dedent"""
{
"data": [
1,
2,
3,
4
]
}
"""
test "max unsigned value":
var uintVal = not uint64(0)
let jsonValue = Json.encode(uintVal)