Improved error handling and pretty-printing for arrays
This commit is contained in:
parent
002c6bbb38
commit
c2daa5a23c
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue