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

View File

@ -1,22 +1,26 @@
import import
strutils, typetraits, macros, strutils, typetraits, macros, strformat,
faststreams/input_stream, serialization/object_serialization, faststreams/input_stream, serialization/[object_serialization, errors],
types, lexer types, lexer
export export
types types, errors
type type
JsonReader* = object JsonReader* = object
lexer: JsonLexer lexer: JsonLexer
JsonReaderError* = object of CatchableError JsonReaderError* = object of JsonError
line*, col*: int line*, col*: int
UnexpectedField* = object of JsonReaderError UnexpectedField* = object of JsonReaderError
encounteredField*: cstring encounteredField*: cstring
deserializedType*: cstring deserializedType*: cstring
CustomSerializationError* = object of JsonReaderError
deserializedType*: cstring
innerException*: ref CatchableError
ExpectedTokenCategory* = enum ExpectedTokenCategory* = enum
etBool = "bool literal" etBool = "bool literal"
etInt = "integer" etInt = "integer"
@ -33,16 +37,21 @@ type
encountedToken*: TokKind encountedToken*: TokKind
expectedToken*: ExpectedTokenCategory expectedToken*: ExpectedTokenCategory
proc init*(T: type JsonReader, stream: AsciiStreamVar, mode = defaultJsonMode): T = method formatMsg*(err: ref JsonReaderError, filename: string): string =
result.lexer = JsonLexer.init(stream, mode) fmt"{filename}({err.line}, {err.col}) Error while reading json file"
result.lexer.next()
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 = template init*(T: type JsonReader, stream: ByteStreamVar, mode = defaultJsonMode): auto =
init JsonReader, AsciiStreamVar(stream), mode 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) = proc assignLineNumber(ex: ref JsonReaderError, r: JsonReader) =
ex.line = r.lexer.line ex.line = r.lexer.line
ex.col = r.lexer.col ex.col = r.lexer.col
@ -61,6 +70,22 @@ proc raiseUnexpectedField(r: JsonReader, fieldName, deserializedType: cstring) =
ex.deserializedType = deserializedType ex.deserializedType = deserializedType
raise ex 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) = proc requireToken(r: JsonReader, tk: TokKind) =
if r.lexer.tok != tk: if r.lexer.tok != tk:
r.raiseUnexpectedToken case tk r.raiseUnexpectedToken case tk
@ -77,7 +102,31 @@ proc skipToken(r: var JsonReader, tk: TokKind) =
r.requireToken tk r.requireToken tk
r.lexer.next() 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 mixin readValue
let tok = r.lexer.tok let tok = r.lexer.tok
@ -126,7 +175,7 @@ proc readImpl(r: var JsonReader, value: var auto) =
while true: while true:
let lastPos = value.len let lastPos = value.len
value.setLen(lastPos + 1) value.setLen(lastPos + 1)
readValue(r, value[lastPos]) checkedRead(r, value[lastPos])
if r.lexer.tok != tkComma: break if r.lexer.tok != tkComma: break
r.lexer.next() r.lexer.next()
r.skipToken tkBracketRi r.skipToken tkBracketRi
@ -136,9 +185,9 @@ proc readImpl(r: var JsonReader, value: var auto) =
for i in low(value) ..< high(value): for i in low(value) ..< high(value):
# TODO: dont's ask. this makes the code compile # TODO: dont's ask. this makes the code compile
if false: value[i] = value[i] if false: value[i] = value[i]
readValue(r, value[i]) checkedRead(r, value[i])
r.skipToken tkComma r.skipToken tkComma
readValue(r, value[high(value)]) checkedRead(r, value[high(value)])
r.skipToken tkBracketRi r.skipToken tkBracketRi
elif value is (object or tuple): elif value is (object or tuple):
@ -168,6 +217,3 @@ proc readImpl(r: var JsonReader, value: var auto) =
const typeName = typetraits.name(value.type) const typeName = typetraits.name(value.type)
{.error: "Failed to convert to JSON an unsupported type: " & typeName.} {.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 type
JsonMode* = enum JsonMode* = enum
Relaxed Relaxed
Portable Portable
JsonError* = object of CatchableError JsonError* = object of SerializationError
const const
defaultJsonMode* = JsonMode.Relaxed defaultJsonMode* = JsonMode.Relaxed

View File

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

View File

@ -22,6 +22,9 @@ type
z: ref Simple z: ref Simple
# o: Option[Simple] # o: Option[Simple]
HoldsArray = object
data: seq[int]
Invalid = object Invalid = object
distance: Mile 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": test "max unsigned value":
var uintVal = not uint64(0) var uintVal = not uint64(0)
let jsonValue = Json.encode(uintVal) let jsonValue = Json.encode(uintVal)