205 lines
6.8 KiB
Nim
205 lines
6.8 KiB
Nim
# json-serialization
|
|
# Copyright (c) 2019-2023 Status Research & Development GmbH
|
|
# Licensed under either of
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
# at your option.
|
|
# This file may not be copied, modified, or distributed except according to
|
|
# those terms.
|
|
|
|
{.experimental: "notnil".}
|
|
|
|
import
|
|
std/[strformat],
|
|
faststreams/inputs,
|
|
serialization/[formats, errors, object_serialization],
|
|
"."/[format, types, lexer]
|
|
|
|
export
|
|
inputs, format, types, errors,
|
|
DefaultFlavor
|
|
|
|
type
|
|
JsonReader*[Flavor = DefaultFlavor] = object
|
|
lex*: JsonLexer
|
|
|
|
JsonReaderError* = object of JsonError
|
|
line*, col*: int
|
|
|
|
UnexpectedField* = object of JsonReaderError
|
|
encounteredField*: string
|
|
deserializedType*: cstring
|
|
|
|
ExpectedTokenCategory* = enum
|
|
etValue = "value"
|
|
etBool = "bool literal"
|
|
etInt = "integer"
|
|
etEnumAny = "enum value (int / string)"
|
|
etEnumString = "enum value (string)"
|
|
etNumber = "number"
|
|
etString = "string"
|
|
etComma = "comma"
|
|
etColon = "colon"
|
|
etBracketLe = "array start bracket"
|
|
etBracketRi = "array end bracker"
|
|
etCurrlyLe = "object start bracket"
|
|
etCurrlyRi = "object end bracket"
|
|
|
|
GenericJsonReaderError* = object of JsonReaderError
|
|
deserializedField*: string
|
|
innerException*: ref CatchableError
|
|
|
|
UnexpectedTokenError* = object of JsonReaderError
|
|
encountedToken*: JsonValueKind
|
|
expectedToken*: ExpectedTokenCategory
|
|
|
|
UnexpectedValueError* = object of JsonReaderError
|
|
|
|
IncompleteObjectError* = object of JsonReaderError
|
|
objectType: cstring
|
|
|
|
IntOverflowError* = object of JsonReaderError
|
|
isNegative: bool
|
|
absIntVal: BiggestUInt
|
|
|
|
Json.setReader JsonReader
|
|
|
|
{.push gcsafe, raises: [].}
|
|
|
|
func valueStr(err: ref IntOverflowError): string =
|
|
if err.isNegative:
|
|
result.add '-'
|
|
result.add($err.absIntVal)
|
|
|
|
template tryFmt(expr: untyped): string =
|
|
try: expr
|
|
except CatchableError as err: err.msg
|
|
|
|
method formatMsg*(err: ref JsonReaderError, filename: string):
|
|
string {.gcsafe, raises: [].} =
|
|
tryFmt: fmt"{filename}({err.line}, {err.col}) Error while reading json file: {err.msg}"
|
|
|
|
method formatMsg*(err: ref UnexpectedField, filename: string):
|
|
string {.gcsafe, raises: [].} =
|
|
tryFmt: fmt"{filename}({err.line}, {err.col}) Unexpected field '{err.encounteredField}' while deserializing {err.deserializedType}"
|
|
|
|
method formatMsg*(err: ref UnexpectedTokenError, filename: string):
|
|
string {.gcsafe, raises: [].} =
|
|
tryFmt: fmt"{filename}({err.line}, {err.col}) Unexpected token '{err.encountedToken}' in place of '{err.expectedToken}'"
|
|
|
|
method formatMsg*(err: ref GenericJsonReaderError, filename: string):
|
|
string {.gcsafe, raises: [].} =
|
|
tryFmt: fmt"{filename}({err.line}, {err.col}) Exception encountered while deserializing '{err.deserializedField}': [{err.innerException.name}] {err.innerException.msg}"
|
|
|
|
method formatMsg*(err: ref IntOverflowError, filename: string):
|
|
string {.gcsafe, raises: [].} =
|
|
tryFmt: fmt"{filename}({err.line}, {err.col}) The value '{err.valueStr}' is outside of the allowed range"
|
|
|
|
method formatMsg*(err: ref UnexpectedValueError, filename: string):
|
|
string {.gcsafe, raises: [].} =
|
|
tryFmt: fmt"{filename}({err.line}, {err.col}) {err.msg}"
|
|
|
|
method formatMsg*(err: ref IncompleteObjectError, filename: string):
|
|
string {.gcsafe, raises: [].} =
|
|
tryFmt: fmt"{filename}({err.line}, {err.col}) Not all required fields were specified when reading '{err.objectType}'"
|
|
|
|
func assignLineNumber*(ex: ref JsonReaderError, lex: JsonLexer) =
|
|
ex.line = lex.line
|
|
ex.col = lex.tokenStartCol
|
|
|
|
proc raiseUnexpectedToken*(lex: var JsonLexer, expected: ExpectedTokenCategory)
|
|
{.noreturn, raises: [JsonReaderError, IOError].} =
|
|
var ex = new UnexpectedTokenError
|
|
ex.assignLineNumber(lex)
|
|
ex.encountedToken = lex.tokKind
|
|
ex.expectedToken = expected
|
|
raise ex
|
|
|
|
template raiseUnexpectedToken*(reader: JsonReader, expected: ExpectedTokenCategory) =
|
|
raiseUnexpectedToken(reader.lex, expected)
|
|
|
|
func raiseUnexpectedValue*(
|
|
lex: JsonLexer, msg: string) {.noreturn, raises: [JsonReaderError].} =
|
|
var ex = new UnexpectedValueError
|
|
ex.assignLineNumber(lex)
|
|
ex.msg = msg
|
|
raise ex
|
|
|
|
template raiseUnexpectedValue*(r: JsonReader, msg: string) =
|
|
raiseUnexpectedValue(r.lex, msg)
|
|
|
|
func raiseIntOverflow*(
|
|
lex: JsonLexer, absIntVal: BiggestUInt, isNegative: bool)
|
|
{.noreturn, raises: [JsonReaderError].} =
|
|
var ex = new IntOverflowError
|
|
ex.assignLineNumber(lex)
|
|
ex.absIntVal = absIntVal
|
|
ex.isNegative = isNegative
|
|
raise ex
|
|
|
|
template raiseIntOverflow*(r: JsonReader, absIntVal: BiggestUInt, isNegative: bool) =
|
|
raiseIntOverflow(r.lex, absIntVal, isNegative)
|
|
|
|
func raiseUnexpectedField*(
|
|
lex: JsonLexer, fieldName: string, deserializedType: cstring)
|
|
{.noreturn, raises: [JsonReaderError].} =
|
|
var ex = new UnexpectedField
|
|
ex.assignLineNumber(lex)
|
|
ex.encounteredField = fieldName
|
|
ex.deserializedType = deserializedType
|
|
raise ex
|
|
|
|
template raiseUnexpectedField*(r: JsonReader, fieldName: string, deserializedType: cstring) =
|
|
raiseUnexpectedField(r.lex, fieldName, deserializedType)
|
|
|
|
func raiseIncompleteObject*(
|
|
lex: JsonLexer, objectType: cstring)
|
|
{.noreturn, raises: [JsonReaderError].} =
|
|
var ex = new IncompleteObjectError
|
|
ex.assignLineNumber(lex)
|
|
ex.objectType = objectType
|
|
raise ex
|
|
|
|
template raiseIncompleteObject*(r: JsonReader, objectType: cstring) =
|
|
raiseIncompleteObject(r.lex, objectType)
|
|
|
|
func handleReadException*(lex: JsonLexer,
|
|
Record: type,
|
|
fieldName: string,
|
|
field: auto,
|
|
err: ref CatchableError) {.raises: [JsonReaderError].} =
|
|
var ex = new GenericJsonReaderError
|
|
ex.assignLineNumber(lex)
|
|
ex.deserializedField = fieldName
|
|
ex.innerException = err
|
|
raise ex
|
|
|
|
template handleReadException*(r: JsonReader,
|
|
Record: type,
|
|
fieldName: string,
|
|
field: auto,
|
|
err: ref CatchableError) =
|
|
handleReadException(r.lex, Record, fieldName, field, err)
|
|
|
|
proc init*(T: type JsonReader,
|
|
stream: InputStream,
|
|
flags: JsonReaderFlags,
|
|
conf: JsonReaderConf = defaultJsonReaderConf): T {.raises: [].} =
|
|
result.lex = JsonLexer.init(stream, flags, conf)
|
|
|
|
proc init*(T: type JsonReader,
|
|
stream: InputStream,
|
|
allowUnknownFields = false,
|
|
requireAllFields = false): T {.raises: [].} =
|
|
mixin flavorAllowsUnknownFields, flavorRequiresAllFields
|
|
type Flavor = T.Flavor
|
|
|
|
var flags = defaultJsonReaderFlags
|
|
if allowUnknownFields or flavorAllowsUnknownFields(Flavor):
|
|
flags.incl JsonReaderFlag.allowUnknownFields
|
|
if requireAllFields or flavorRequiresAllFields(Flavor):
|
|
flags.incl JsonReaderFlag.requireAllFields
|
|
result.lex = JsonLexer.init(stream, flags)
|
|
|
|
{.pop.}
|