mirror of
https://github.com/logos-storage/nim-ethers.git
synced 2026-01-10 01:23:05 +00:00
use nim-serde instead of json util
Allows aliasing of de/serialized fields, so revert changes of sender to `from` and transactionType to `type`
This commit is contained in:
parent
4a5028f295
commit
fae4339ab6
@ -9,6 +9,7 @@ requires "chronos >= 4.0.0 & < 4.1.0"
|
||||
requires "contractabi >= 0.6.0 & < 0.7.0"
|
||||
requires "questionable >= 0.10.2 & < 0.11.0"
|
||||
requires "json_rpc >= 0.4.0 & < 0.5.0"
|
||||
requires "serde >= 0.1.1 & < 0.2.0"
|
||||
requires "stint"
|
||||
requires "stew"
|
||||
requires "eth"
|
||||
|
||||
@ -101,8 +101,8 @@ proc call(contract: Contract,
|
||||
overrides = TransactionOverrides()) {.async.} =
|
||||
var transaction = createTransaction(contract, function, parameters, overrides)
|
||||
|
||||
if signer =? contract.signer and transaction.`from`.isNone:
|
||||
transaction.`from` = some(await signer.getAddress())
|
||||
if signer =? contract.signer and transaction.sender.isNone:
|
||||
transaction.sender = some(await signer.getAddress())
|
||||
|
||||
discard await contract.provider.call(transaction, overrides)
|
||||
|
||||
@ -114,8 +114,8 @@ proc call(contract: Contract,
|
||||
overrides = TransactionOverrides()): Future[ReturnType] {.async.} =
|
||||
var transaction = createTransaction(contract, function, parameters, overrides)
|
||||
|
||||
if signer =? contract.signer and transaction.`from`.isNone:
|
||||
transaction.`from` = some(await signer.getAddress())
|
||||
if signer =? contract.signer and transaction.sender.isNone:
|
||||
transaction.sender = some(await signer.getAddress())
|
||||
|
||||
let response = await contract.provider.call(transaction, overrides)
|
||||
return decodeResponse(ReturnType, returnMultiple, response)
|
||||
|
||||
@ -76,5 +76,5 @@ method transferFrom*(token: Erc20Token,
|
||||
spender: Address,
|
||||
recipient: Address,
|
||||
amount: UInt256): ?TransactionResponse {.base, contract.}
|
||||
## Moves `amount` tokens from `from` to `to` using the allowance
|
||||
## Moves `amount` tokens from sender to `to` using the allowance
|
||||
## mechanism. `amount` is then deducted from the caller's allowance.
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import pkg/chronicles
|
||||
import pkg/serde
|
||||
import pkg/stew/byteutils
|
||||
import ./basics
|
||||
import ./transaction
|
||||
import ./blocktag
|
||||
import ./providers/jsonrpc/json
|
||||
|
||||
export basics
|
||||
export transaction
|
||||
@ -15,20 +15,20 @@ type
|
||||
Provider* = ref object of RootObj
|
||||
ProviderError* = object of EthersError
|
||||
Subscription* = ref object of RootObj
|
||||
EventFilter* = ref object of RootObj
|
||||
address* {.serialize.}: Address
|
||||
topics* {.serialize.}: seq[Topic]
|
||||
Filter* = ref object of EventFilter
|
||||
fromBlock* {.serialize.}: BlockTag
|
||||
toBlock* {.serialize.}: BlockTag
|
||||
FilterByBlockHash* = ref object of EventFilter
|
||||
blockHash* {.serialize.}: BlockHash
|
||||
Log* = object
|
||||
blockNumber* {.serialize.}: UInt256
|
||||
data* {.serialize.}: seq[byte]
|
||||
logIndex* {.serialize.}: UInt256
|
||||
removed* {.serialize.}: bool
|
||||
topics* {.serialize.}: seq[Topic]
|
||||
EventFilter* {.serialize.} = ref object of RootObj
|
||||
address*: Address
|
||||
topics*: seq[Topic]
|
||||
Filter* {.serialize.} = ref object of EventFilter
|
||||
fromBlock*: BlockTag
|
||||
toBlock*: BlockTag
|
||||
FilterByBlockHash* {.serialize.} = ref object of EventFilter
|
||||
blockHash*: BlockHash
|
||||
Log* {.serialize.} = object
|
||||
blockNumber*: UInt256
|
||||
data*: seq[byte]
|
||||
logIndex*: UInt256
|
||||
removed*: bool
|
||||
topics*: seq[Topic]
|
||||
TransactionHash* = array[32, byte]
|
||||
BlockHash* = array[32, byte]
|
||||
TransactionStatus* = enum
|
||||
@ -38,43 +38,43 @@ type
|
||||
TransactionResponse* = object
|
||||
provider*: Provider
|
||||
hash* {.serialize.}: TransactionHash
|
||||
TransactionReceipt* = object
|
||||
`from`* {.serialize.}: ?Address
|
||||
to* {.serialize.}: ?Address
|
||||
contractAddress* {.serialize.}: ?Address
|
||||
transactionIndex* {.serialize.}: UInt256
|
||||
gasUsed* {.serialize.}: UInt256
|
||||
logsBloom* {.serialize.}: seq[byte]
|
||||
blockHash* {.serialize.}: ?BlockHash
|
||||
transactionHash* {.serialize.}: TransactionHash
|
||||
logs* {.serialize.}: seq[Log]
|
||||
blockNumber* {.serialize.}: ?UInt256
|
||||
cumulativeGasUsed* {.serialize.}: UInt256
|
||||
effectiveGasPrice* {.serialize.}: ?UInt256
|
||||
status* {.serialize.}: TransactionStatus
|
||||
`type`* {.serialize.}: TransactionType
|
||||
TransactionReceipt* {.serialize.} = object
|
||||
sender* {.serialize("from"), deserialize("from").}: ?Address
|
||||
to*: ?Address
|
||||
contractAddress*: ?Address
|
||||
transactionIndex*: UInt256
|
||||
gasUsed*: UInt256
|
||||
logsBloom*: seq[byte]
|
||||
blockHash*: ?BlockHash
|
||||
transactionHash*: TransactionHash
|
||||
logs*: seq[Log]
|
||||
blockNumber*: ?UInt256
|
||||
cumulativeGasUsed*: UInt256
|
||||
effectiveGasPrice*: ?UInt256
|
||||
status*: TransactionStatus
|
||||
transactionType* {.serialize("type"), deserialize("type").}: TransactionType
|
||||
LogHandler* = proc(log: Log) {.gcsafe, raises:[].}
|
||||
BlockHandler* = proc(blck: Block) {.gcsafe, raises:[].}
|
||||
Topic* = array[32, byte]
|
||||
Block* = object
|
||||
number* {.serialize.}: ?UInt256
|
||||
timestamp* {.serialize.}: UInt256
|
||||
hash* {.serialize.}: ?BlockHash
|
||||
PastTransaction* = object
|
||||
blockHash* {.serialize.}: BlockHash
|
||||
blockNumber* {.serialize.}: UInt256
|
||||
`from`* {.serialize.}: Address
|
||||
gas* {.serialize.}: UInt256
|
||||
gasPrice* {.serialize.}: UInt256
|
||||
hash* {.serialize.}: TransactionHash
|
||||
input* {.serialize.}: seq[byte]
|
||||
nonce* {.serialize.}: UInt256
|
||||
to* {.serialize.}: Address
|
||||
transactionIndex* {.serialize.}: UInt256
|
||||
`type`* {.serialize.}: ?TransactionType
|
||||
chainId* {.serialize.}: ?UInt256
|
||||
value* {.serialize.}: UInt256
|
||||
v* {.serialize.}, r* {.serialize.}, s* {.serialize.}: UInt256
|
||||
Block* {.serialize.} = object
|
||||
number*: ?UInt256
|
||||
timestamp*: UInt256
|
||||
hash*: ?BlockHash
|
||||
PastTransaction* {.serialize.} = object
|
||||
blockHash*: BlockHash
|
||||
blockNumber*: UInt256
|
||||
sender* {.serialize("from"), deserialize("from").}: Address
|
||||
gas*: UInt256
|
||||
gasPrice*: UInt256
|
||||
hash*: TransactionHash
|
||||
input*: seq[byte]
|
||||
nonce*: UInt256
|
||||
to*: Address
|
||||
transactionIndex*: UInt256
|
||||
transactionType* {.serialize("type"), deserialize("type").}: ?TransactionType
|
||||
chainId*: ?UInt256
|
||||
value*: UInt256
|
||||
v*, r*, s*: UInt256
|
||||
|
||||
const EthersDefaultConfirmations* {.intdefine.} = 12
|
||||
const EthersReceiptTimeoutBlks* {.intdefine.} = 50 # in blocks
|
||||
@ -87,7 +87,7 @@ template raiseProviderError(msg: string) =
|
||||
|
||||
func toTransaction*(past: PastTransaction): Transaction =
|
||||
Transaction(
|
||||
`from`: some past.`from`,
|
||||
sender: some past.sender,
|
||||
to: past.to,
|
||||
data: past.input,
|
||||
value: past.value,
|
||||
@ -95,7 +95,7 @@ func toTransaction*(past: PastTransaction): Transaction =
|
||||
chainId: past.chainId,
|
||||
gasPrice: some past.gasPrice,
|
||||
gasLimit: some past.gas,
|
||||
`type`: past.`type`
|
||||
transactionType: past.transactionType
|
||||
)
|
||||
|
||||
method getBlockNumber*(
|
||||
|
||||
@ -4,15 +4,15 @@ import pkg/chronicles
|
||||
import pkg/eth/common/eth_types_json_serialization
|
||||
import pkg/json_rpc/rpcclient
|
||||
import pkg/json_rpc/errors
|
||||
import pkg/serde
|
||||
import ../basics
|
||||
import ../provider
|
||||
import ../signer
|
||||
import ./jsonrpc/rpccalls
|
||||
import ./jsonrpc/conversions
|
||||
import ./jsonrpc/subscriptions
|
||||
import ./jsonrpc/json
|
||||
|
||||
export json
|
||||
export serde
|
||||
export basics
|
||||
export provider
|
||||
export chronicles
|
||||
|
||||
@ -3,15 +3,15 @@ import std/strutils
|
||||
import pkg/chronicles except fromJson, `%`, `%*`, toJson
|
||||
import pkg/json_rpc/jsonmarshal
|
||||
import pkg/questionable/results
|
||||
import pkg/serde
|
||||
import pkg/stew/byteutils
|
||||
import ../../basics
|
||||
import ../../transaction
|
||||
import ../../blocktag
|
||||
import ../../provider
|
||||
import ./json
|
||||
|
||||
export jsonmarshal
|
||||
export json
|
||||
export serde
|
||||
export chronicles except fromJson, `%`, `%*`, toJson
|
||||
|
||||
{.push raises: [].}
|
||||
@ -62,7 +62,7 @@ func `%`*(transaction: Transaction): JsonNode =
|
||||
"data": %transaction.data,
|
||||
"value": %transaction.value
|
||||
}
|
||||
if sender =? transaction.`from`:
|
||||
if sender =? transaction.sender:
|
||||
result["from"] = %sender
|
||||
if nonce =? transaction.nonce:
|
||||
result["nonce"] = %nonce
|
||||
|
||||
@ -1,537 +0,0 @@
|
||||
|
||||
import std/json as stdjson except `%`, `%*`
|
||||
import std/macros
|
||||
import std/options
|
||||
import std/sequtils
|
||||
import std/sets
|
||||
import std/strutils
|
||||
import std/tables
|
||||
import std/typetraits
|
||||
import pkg/chronicles except toJson
|
||||
import pkg/contractabi
|
||||
import pkg/stew/byteutils
|
||||
import pkg/stint
|
||||
import pkg/questionable
|
||||
import pkg/questionable/results
|
||||
|
||||
import ../../basics
|
||||
|
||||
export stdjson except `%`, `%*`, parseJson
|
||||
export chronicles except toJson
|
||||
export sets
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
logScope:
|
||||
topics = "json de/serialization"
|
||||
|
||||
type
|
||||
SerdeError* = object of EthersError
|
||||
UnexpectedKindError* = object of SerdeError
|
||||
SerdeMode* = enum
|
||||
OptOut, ## serialize: all object fields will be serialized, except fields marked with 'ignore'
|
||||
## deserialize: all json keys will be deserialized, no error if extra json field
|
||||
OptIn, ## serialize: only object fields marked with serialize will be serialzied
|
||||
## deserialize: only fields marked with deserialize will be deserialized
|
||||
Strict ## serialize: all object fields will be serialized, regardless if the field is marked with 'ignore'
|
||||
## deserialize: object fields and json fields must match exactly
|
||||
SerdeFieldOptions = object
|
||||
key: string
|
||||
ignore: bool
|
||||
|
||||
template serialize*(key = "", ignore = false, mode = SerdeMode.OptOut) {.pragma.}
|
||||
template deserialize*(key = "", ignore = false, mode = SerdeMode.OptOut) {.pragma.}
|
||||
|
||||
proc isDefault[T](paramValue: T): bool {.compileTime.} =
|
||||
var result = paramValue == T.default
|
||||
when T is SerdeMode:
|
||||
return paramValue == SerdeMode.OptOut
|
||||
return result
|
||||
|
||||
template expectMissingPragmaParam(value, pragma, name, msg) =
|
||||
static:
|
||||
when value.hasCustomPragma(pragma):
|
||||
const params = value.getCustomPragmaVal(pragma)
|
||||
for paramName, paramValue in params.fieldPairs:
|
||||
|
||||
if paramName == name and not paramValue.isDefault:
|
||||
raiseAssert(msg)
|
||||
|
||||
proc mapErrTo[E1: ref CatchableError, E2: SerdeError](
|
||||
e1: E1,
|
||||
_: type E2,
|
||||
msg: string = e1.msg): ref E2 =
|
||||
|
||||
return newException(E2, msg, e1)
|
||||
|
||||
proc newSerdeError(msg: string): ref SerdeError =
|
||||
newException(SerdeError, msg)
|
||||
|
||||
proc newUnexpectedKindError(
|
||||
expectedType: type,
|
||||
expectedKinds: string,
|
||||
json: JsonNode
|
||||
): ref UnexpectedKindError =
|
||||
let kind = if json.isNil: "nil"
|
||||
else: $json.kind
|
||||
newException(UnexpectedKindError,
|
||||
"deserialization to " & $expectedType & " failed: expected " &
|
||||
expectedKinds & " but got " & $kind)
|
||||
|
||||
proc newUnexpectedKindError(
|
||||
expectedType: type,
|
||||
expectedKinds: set[JsonNodeKind],
|
||||
json: JsonNode
|
||||
): ref UnexpectedKindError =
|
||||
newUnexpectedKindError(expectedType, $expectedKinds, json)
|
||||
|
||||
proc newUnexpectedKindError(
|
||||
expectedType: type,
|
||||
expectedKind: JsonNodeKind,
|
||||
json: JsonNode
|
||||
): ref UnexpectedKindError =
|
||||
newUnexpectedKindError(expectedType, {expectedKind}, json)
|
||||
|
||||
template expectJsonKind(
|
||||
expectedType: type,
|
||||
expectedKinds: set[JsonNodeKind],
|
||||
json: JsonNode
|
||||
) =
|
||||
if json.isNil or json.kind notin expectedKinds:
|
||||
return failure(newUnexpectedKindError(expectedType, expectedKinds, json))
|
||||
|
||||
template expectJsonKind*(
|
||||
expectedType: type,
|
||||
expectedKind: JsonNodeKind,
|
||||
json: JsonNode
|
||||
) =
|
||||
expectJsonKind(expectedType, {expectedKind}, json)
|
||||
|
||||
proc fieldKeys[T](obj: T): seq[string] =
|
||||
for name, _ in fieldPairs(when type(T) is ref: obj[] else: obj):
|
||||
result.add name
|
||||
|
||||
func keysNotIn[T](json: JsonNode, obj: T): HashSet[string] =
|
||||
let jsonKeys = json.keys.toSeq.toHashSet
|
||||
let objKeys = obj.fieldKeys.toHashSet
|
||||
difference(jsonKeys, objKeys)
|
||||
|
||||
template getSerdeFieldOptions(pragma, fieldName, fieldValue): SerdeFieldOptions =
|
||||
var opts = SerdeFieldOptions(key: fieldName, ignore: false)
|
||||
when fieldValue.hasCustomPragma(pragma):
|
||||
fieldValue.expectMissingPragmaParam(pragma, "mode",
|
||||
"Cannot set " & astToStr(pragma) & " 'mode' on '" & fieldName & "' field defintion.")
|
||||
let (key, ignore, _) = fieldValue.getCustomPragmaVal(pragma)
|
||||
opts.ignore = ignore
|
||||
if key != "":
|
||||
opts.key = key
|
||||
opts
|
||||
|
||||
template getSerdeMode(T, pragma): SerdeMode =
|
||||
when T.hasCustomPragma(pragma):
|
||||
T.expectMissingPragmaParam(pragma, "key",
|
||||
"Cannot set " & astToStr(pragma) & " 'key' on '" & $T &
|
||||
"' type definition.")
|
||||
T.expectMissingPragmaParam(pragma, "ignore",
|
||||
"Cannot set " & astToStr(pragma) & " 'ignore' on '" & $T &
|
||||
"' type definition.")
|
||||
let (_, _, mode) = T.getCustomPragmaVal(pragma)
|
||||
mode
|
||||
else:
|
||||
# Default mode -- when the type is NOT annotated with a
|
||||
# serialize/deserialize pragma.
|
||||
#
|
||||
# NOTE This may be different in the logic branch above, when the type is
|
||||
# annotated with serialize/deserialize but doesn't specify a mode. The
|
||||
# default in that case will fallback to the default mode specified in the
|
||||
# pragma signature (currently OptOut for both serialize and deserialize)
|
||||
#
|
||||
# Examples:
|
||||
# 1. type MyObj = object
|
||||
# Type is not annotated, mode defaults to OptOut (as specified on the
|
||||
# pragma signatures) for both serialization and deserializtion
|
||||
#
|
||||
# 2. type MyObj {.serialize, deserialize.} = object
|
||||
# Type is annotated, mode defaults to OptIn for serialization and OptOut
|
||||
# for deserialization
|
||||
when pragma == serialize:
|
||||
SerdeMode.OptIn
|
||||
elif pragma == deserialize:
|
||||
SerdeMode.OptOut
|
||||
|
||||
proc fromJson*(
|
||||
T: type enum,
|
||||
json: JsonNode
|
||||
): ?!T =
|
||||
expectJsonKind(string, JString, json)
|
||||
without val =? parseEnum[T](json.str).catch, error:
|
||||
return failure error.mapErrTo(SerdeError)
|
||||
return success val
|
||||
|
||||
proc fromJson*(
|
||||
_: type string,
|
||||
json: JsonNode
|
||||
): ?!string =
|
||||
if json.isNil:
|
||||
return failure newSerdeError("'json' expected, but was nil")
|
||||
elif json.kind == JNull:
|
||||
return success("null")
|
||||
elif json.isNil or json.kind != JString:
|
||||
return failure newUnexpectedKindError(string, JString, json)
|
||||
catch json.getStr
|
||||
|
||||
proc fromJson*(
|
||||
_: type bool,
|
||||
json: JsonNode
|
||||
): ?!bool =
|
||||
expectJsonKind(bool, JBool, json)
|
||||
catch json.getBool
|
||||
|
||||
proc fromJson*(
|
||||
_: type int,
|
||||
json: JsonNode
|
||||
): ?!int =
|
||||
expectJsonKind(int, JInt, json)
|
||||
catch json.getInt
|
||||
|
||||
proc fromJson*[T: SomeInteger](
|
||||
_: type T,
|
||||
json: JsonNode
|
||||
): ?!T =
|
||||
when T is uint|uint64 or (not defined(js) and int.sizeof == 4):
|
||||
expectJsonKind(T, {JInt, JString}, json)
|
||||
case json.kind
|
||||
of JString:
|
||||
without x =? parseBiggestUInt(json.str).catch, error:
|
||||
return failure newSerdeError(error.msg)
|
||||
return success cast[T](x)
|
||||
else:
|
||||
return success T(json.num)
|
||||
else:
|
||||
expectJsonKind(T, {JInt}, json)
|
||||
return success cast[T](json.num)
|
||||
|
||||
proc fromJson*[T: SomeFloat](
|
||||
_: type T,
|
||||
json: JsonNode
|
||||
): ?!T =
|
||||
expectJsonKind(T, {JInt, JFloat, JString}, json)
|
||||
if json.kind == JString:
|
||||
case json.str
|
||||
of "nan":
|
||||
let b = NaN
|
||||
return success T(b)
|
||||
# dst = NaN # would fail some tests because range conversions would cause CT error
|
||||
# in some cases; but this is not a hot-spot inside this branch and backend can optimize this.
|
||||
of "inf":
|
||||
let b = Inf
|
||||
return success T(b)
|
||||
of "-inf":
|
||||
let b = -Inf
|
||||
return success T(b)
|
||||
else:
|
||||
let err = newUnexpectedKindError(T, "'nan|inf|-inf'", json)
|
||||
return failure(err)
|
||||
else:
|
||||
if json.kind == JFloat:
|
||||
return success T(json.fnum)
|
||||
else:
|
||||
return success T(json.num)
|
||||
|
||||
proc fromJson*(
|
||||
_: type seq[byte],
|
||||
json: JsonNode
|
||||
): ?!seq[byte] =
|
||||
expectJsonKind(seq[byte], JString, json)
|
||||
hexToSeqByte(json.getStr).catch
|
||||
|
||||
proc fromJson*[N: static[int], T: array[N, byte]](
|
||||
_: type T,
|
||||
json: JsonNode
|
||||
): ?!T =
|
||||
expectJsonKind(T, JString, json)
|
||||
T.fromHex(json.getStr).catch
|
||||
|
||||
proc fromJson*[T: distinct](
|
||||
_: type T,
|
||||
json: JsonNode
|
||||
): ?!T =
|
||||
success T(? T.distinctBase.fromJson(json))
|
||||
|
||||
proc fromJson*[N: static[int], T: StUint[N]](
|
||||
_: type T,
|
||||
json: JsonNode
|
||||
): ?!T =
|
||||
expectJsonKind(T, JString, json)
|
||||
let jsonStr = json.getStr
|
||||
let prefix = jsonStr[0..1].toLowerAscii
|
||||
case prefix:
|
||||
of "0x": catch parse(jsonStr, T, 16)
|
||||
of "0o": catch parse(jsonStr, T, 8)
|
||||
of "0b": catch parse(jsonStr, T, 2)
|
||||
else: catch parse(jsonStr, T)
|
||||
|
||||
proc fromJson*[T](
|
||||
_: type Option[T],
|
||||
json: JsonNode
|
||||
): ?! Option[T] =
|
||||
if json.isNil or json.kind == JNull:
|
||||
return success(none T)
|
||||
without val =? T.fromJson(json), error:
|
||||
return failure(error)
|
||||
success(val.some)
|
||||
|
||||
proc fromJson*[T](
|
||||
_: type seq[T],
|
||||
json: JsonNode
|
||||
): ?! seq[T] =
|
||||
expectJsonKind(seq[T], JArray, json)
|
||||
var arr: seq[T] = @[]
|
||||
for elem in json.elems:
|
||||
arr.add(? T.fromJson(elem))
|
||||
success arr
|
||||
|
||||
proc fromJson*[T: ref object or object](
|
||||
_: type T,
|
||||
json: JsonNode
|
||||
): ?!T =
|
||||
|
||||
when T is JsonNode:
|
||||
return success T(json)
|
||||
|
||||
expectJsonKind(T, JObject, json)
|
||||
var res = when type(T) is ref: T.new() else: T.default
|
||||
let mode = T.getSerdeMode(deserialize)
|
||||
|
||||
# ensure there's no extra fields in json
|
||||
if mode == SerdeMode.Strict:
|
||||
let extraFields = json.keysNotIn(res)
|
||||
if extraFields.len > 0:
|
||||
return failure newSerdeError("json field(s) missing in object: " & $extraFields)
|
||||
|
||||
for name, value in fieldPairs(when type(T) is ref: res[] else: res):
|
||||
|
||||
logScope:
|
||||
field = $T & "." & name
|
||||
mode
|
||||
|
||||
let hasDeserializePragma = value.hasCustomPragma(deserialize)
|
||||
let opts = getSerdeFieldOptions(deserialize, name, value)
|
||||
let isOptionalValue = typeof(value) is Option
|
||||
var skip = false # workaround for 'continue' not supported in a 'fields' loop
|
||||
|
||||
case mode:
|
||||
of Strict:
|
||||
if opts.key notin json:
|
||||
return failure newSerdeError("object field missing in json: " & opts.key)
|
||||
elif opts.ignore:
|
||||
# unable to figure out a way to make this a compile time check
|
||||
warn "object field marked as 'ignore' while in Strict mode, field will be deserialized anyway"
|
||||
|
||||
of OptIn:
|
||||
if not hasDeserializePragma:
|
||||
debug "object field not marked as 'deserialize', skipping"
|
||||
skip = true
|
||||
elif opts.ignore:
|
||||
debug "object field marked as 'ignore', skipping"
|
||||
skip = true
|
||||
elif opts.key notin json and not isOptionalValue:
|
||||
return failure newSerdeError("object field missing in json: " & opts.key)
|
||||
|
||||
of OptOut:
|
||||
if opts.ignore:
|
||||
debug "object field is opted out of deserialization ('igore' is set), skipping"
|
||||
skip = true
|
||||
elif hasDeserializePragma and opts.key == name:
|
||||
warn "object field marked as deserialize in OptOut mode, but 'ignore' not set, field will be deserialized"
|
||||
|
||||
if not skip:
|
||||
|
||||
if isOptionalValue:
|
||||
|
||||
let jsonVal = json{opts.key}
|
||||
without parsed =? typeof(value).fromJson(jsonVal), e:
|
||||
debug "failed to deserialize field",
|
||||
`type` = $typeof(value),
|
||||
json = jsonVal,
|
||||
error = e.msg
|
||||
return failure(e)
|
||||
value = parsed
|
||||
|
||||
# not Option[T]
|
||||
elif opts.key in json and
|
||||
jsonVal =? json{opts.key}.catch and
|
||||
not jsonVal.isNil:
|
||||
|
||||
without parsed =? typeof(value).fromJson(jsonVal), e:
|
||||
debug "failed to deserialize field",
|
||||
`type` = $typeof(value),
|
||||
json = jsonVal,
|
||||
error = e.msg
|
||||
return failure(e)
|
||||
value = parsed
|
||||
|
||||
success(res)
|
||||
|
||||
proc parseJson*(json: string): ?!JsonNode =
|
||||
## fix for nim raising Exception
|
||||
try:
|
||||
return stdjson.parseJson(json).catch
|
||||
except Exception as e:
|
||||
return err newException(CatchableError, e.msg)
|
||||
|
||||
proc fromJson*[T: ref object or object](
|
||||
_: type T,
|
||||
bytes: seq[byte]
|
||||
): ?!T =
|
||||
let json = ? parse(string.fromBytes(bytes))
|
||||
T.fromJson(json)
|
||||
|
||||
proc fromJson*(
|
||||
_: type JsonNode,
|
||||
jsn: string
|
||||
): ?!JsonNode =
|
||||
return json.parseJson(jsn)
|
||||
|
||||
proc fromJson*[T: ref object or object](
|
||||
_: type T,
|
||||
jsn: string
|
||||
): ?!T =
|
||||
let jsn = ? json.parseJson(jsn) # full qualification required in-module only
|
||||
T.fromJson(jsn)
|
||||
|
||||
func `%`*(s: string): JsonNode = newJString(s)
|
||||
|
||||
func `%`*(n: uint): JsonNode =
|
||||
if n > cast[uint](int.high):
|
||||
newJString($n)
|
||||
else:
|
||||
newJInt(BiggestInt(n))
|
||||
|
||||
func `%`*(n: int): JsonNode = newJInt(n)
|
||||
|
||||
func `%`*(n: BiggestUInt): JsonNode =
|
||||
if n > cast[BiggestUInt](BiggestInt.high):
|
||||
newJString($n)
|
||||
else:
|
||||
newJInt(BiggestInt(n))
|
||||
|
||||
func `%`*(n: BiggestInt): JsonNode = newJInt(n)
|
||||
|
||||
func `%`*(n: float): JsonNode =
|
||||
if n != n: newJString("nan")
|
||||
elif n == Inf: newJString("inf")
|
||||
elif n == -Inf: newJString("-inf")
|
||||
else: newJFloat(n)
|
||||
|
||||
func `%`*(b: bool): JsonNode = newJBool(b)
|
||||
|
||||
func `%`*(keyVals: openArray[tuple[key: string, val: JsonNode]]): JsonNode =
|
||||
if keyVals.len == 0: return newJArray()
|
||||
let jObj = newJObject()
|
||||
for key, val in items(keyVals): jObj.fields[key] = val
|
||||
jObj
|
||||
|
||||
template `%`*(j: JsonNode): JsonNode = j
|
||||
|
||||
func `%`*[T](table: Table[string, T]|OrderedTable[string, T]): JsonNode =
|
||||
let jObj = newJObject()
|
||||
for k, v in table: jObj[k] = ? %v
|
||||
jObj
|
||||
|
||||
func `%`*[T](opt: Option[T]): JsonNode =
|
||||
if opt.isSome: %(opt.get) else: newJNull()
|
||||
|
||||
|
||||
proc `%`*[T: object or ref object](obj: T): JsonNode =
|
||||
|
||||
let jsonObj = newJObject()
|
||||
let o = when T is ref object: obj[]
|
||||
else: obj
|
||||
|
||||
let mode = T.getSerdeMode(serialize)
|
||||
|
||||
for name, value in o.fieldPairs:
|
||||
|
||||
logScope:
|
||||
field = $T & "." & name
|
||||
mode
|
||||
|
||||
let opts = getSerdeFieldOptions(serialize, name, value)
|
||||
const serializeField = value.hasCustomPragma(serialize)
|
||||
var skip = false # workaround for 'continue' not supported in a 'fields' loop
|
||||
|
||||
case mode:
|
||||
of OptIn:
|
||||
if not serializeField:
|
||||
debug "object field not marked with serialize, skipping"
|
||||
skip = true
|
||||
elif opts.ignore:
|
||||
skip = true
|
||||
|
||||
of OptOut:
|
||||
if opts.ignore:
|
||||
debug "object field opted out of serialization ('ignore' is set), skipping"
|
||||
skip = true
|
||||
elif serializeField and opts.key == name: # all serialize params are default
|
||||
warn "object field marked as serialize in OptOut mode, but 'ignore' not set, field will be serialized"
|
||||
|
||||
of Strict:
|
||||
if opts.ignore:
|
||||
# unable to figure out a way to make this a compile time check
|
||||
warn "object field marked as 'ignore' while in Strict mode, field will be serialized anyway"
|
||||
|
||||
if not skip:
|
||||
jsonObj[opts.key] = %value
|
||||
|
||||
jsonObj
|
||||
|
||||
proc `%`*(o: enum): JsonNode = % $o
|
||||
|
||||
func `%`*(stint: StInt|StUint): JsonNode = %stint.toString
|
||||
|
||||
func `%`*(cstr: cstring): JsonNode = % $cstr
|
||||
|
||||
func `%`*(arr: openArray[byte]): JsonNode = % arr.to0xHex
|
||||
|
||||
func `%`*[T](elements: openArray[T]): JsonNode =
|
||||
let jObj = newJArray()
|
||||
for elem in elements: jObj.add(%elem)
|
||||
jObj
|
||||
|
||||
func `%`*[T: distinct](id: T): JsonNode =
|
||||
type baseType = T.distinctBase
|
||||
% baseType(id)
|
||||
|
||||
proc toJson*[T](item: T): string = $(%item)
|
||||
|
||||
proc toJsnImpl(x: NimNode): NimNode =
|
||||
case x.kind
|
||||
of nnkBracket: # array
|
||||
if x.len == 0: return newCall(bindSym"newJArray")
|
||||
result = newNimNode(nnkBracket)
|
||||
for i in 0 ..< x.len:
|
||||
result.add(toJsnImpl(x[i]))
|
||||
result = newCall(bindSym("%", brOpen), result)
|
||||
of nnkTableConstr: # object
|
||||
if x.len == 0: return newCall(bindSym"newJObject")
|
||||
result = newNimNode(nnkTableConstr)
|
||||
for i in 0 ..< x.len:
|
||||
x[i].expectKind nnkExprColonExpr
|
||||
result.add newTree(nnkExprColonExpr, x[i][0], toJsnImpl(x[i][1]))
|
||||
result = newCall(bindSym("%", brOpen), result)
|
||||
of nnkCurly: # empty object
|
||||
x.expectLen(0)
|
||||
result = newCall(bindSym"newJObject")
|
||||
of nnkNilLit:
|
||||
result = newCall(bindSym"newJNull")
|
||||
of nnkPar:
|
||||
if x.len == 1: result = toJsnImpl(x[0])
|
||||
else: result = newCall(bindSym("%", brOpen), x)
|
||||
else:
|
||||
result = newCall(bindSym("%", brOpen), x)
|
||||
|
||||
macro `%*`*(x: untyped): JsonNode =
|
||||
## Convert an expression to a JsonNode directly, without having to specify
|
||||
## `%` for every element.
|
||||
result = toJsnImpl(x)
|
||||
@ -3,12 +3,12 @@ import std/tables
|
||||
import std/sequtils
|
||||
import pkg/chronos
|
||||
import pkg/json_rpc/rpcclient
|
||||
import pkg/serde
|
||||
import ../../basics
|
||||
import ../../provider
|
||||
import ./rpccalls
|
||||
import ./conversions
|
||||
import ./looping
|
||||
import ./json
|
||||
|
||||
type
|
||||
JsonRpcSubscriptions* = ref object of RootObj
|
||||
|
||||
@ -84,7 +84,7 @@ method estimateGas*(
|
||||
convertError:
|
||||
address = await signer.getAddress
|
||||
|
||||
transaction.`from` = some(address)
|
||||
transaction.sender = some(address)
|
||||
try:
|
||||
return await signer.provider.estimateGas(transaction)
|
||||
except ProviderError as e:
|
||||
@ -132,7 +132,7 @@ method populateTransaction*(
|
||||
convertError:
|
||||
address = await signer.getAddress()
|
||||
|
||||
if sender =? transaction.`from` and sender != address:
|
||||
if sender =? transaction.sender and sender != address:
|
||||
raiseSignerError("from address mismatch")
|
||||
if chainId =? transaction.chainId and chainId != await signer.getChainId():
|
||||
raiseSignerError("chain id mismatch")
|
||||
@ -145,8 +145,8 @@ method populateTransaction*(
|
||||
var populated = transaction
|
||||
|
||||
try:
|
||||
if transaction.`from`.isNone:
|
||||
populated.`from` = some(address)
|
||||
if transaction.sender.isNone:
|
||||
populated.sender = some(address)
|
||||
if transaction.chainId.isNone:
|
||||
populated.chainId = some(await signer.getChainId())
|
||||
if transaction.gasPrice.isNone and (transaction.maxFee.isNone or transaction.maxPriorityFee.isNone):
|
||||
@ -185,7 +185,7 @@ method cancelTransaction*(
|
||||
# cancels a transaction by sending with a 0-valued transaction to ourselves
|
||||
# with the failed tx's nonce
|
||||
|
||||
without sender =? tx.`from`:
|
||||
without sender =? tx.sender:
|
||||
raiseSignerError "transaction must have sender"
|
||||
without nonce =? tx.nonce:
|
||||
raiseSignerError "transaction must have nonce"
|
||||
|
||||
@ -74,7 +74,7 @@ method getAddress*(
|
||||
|
||||
proc signTransaction*(wallet: Wallet,
|
||||
transaction: Transaction): Future[seq[byte]] {.async: (raises:[WalletError]).} =
|
||||
if sender =? transaction.`from` and sender != wallet.address:
|
||||
if sender =? transaction.sender and sender != wallet.address:
|
||||
raiseWalletError "from address mismatch"
|
||||
|
||||
return wallet.privateKey.sign(transaction)
|
||||
|
||||
@ -1,28 +1,28 @@
|
||||
import pkg/serde
|
||||
import pkg/stew/byteutils
|
||||
import ./basics
|
||||
import ./providers/jsonrpc/json
|
||||
|
||||
type
|
||||
TransactionType* = enum
|
||||
Legacy = 0,
|
||||
AccessList = 1,
|
||||
Dynamic = 2
|
||||
Transaction* = object
|
||||
`from`* {.serialize.}: ?Address
|
||||
to* {.serialize.}: Address
|
||||
data* {.serialize.}: seq[byte]
|
||||
value* {.serialize.}: UInt256
|
||||
nonce* {.serialize.}: ?UInt256
|
||||
chainId* {.serialize.}: ?UInt256
|
||||
gasPrice* {.serialize.}: ?UInt256
|
||||
maxFee* {.serialize.}: ?UInt256
|
||||
maxPriorityFee* {.serialize.}: ?UInt256
|
||||
gasLimit* {.serialize.}: ?UInt256
|
||||
`type`* {.serialize.}: ?TransactionType
|
||||
Transaction* {.serialize.} = object
|
||||
sender* {.serialize("from").}: ?Address
|
||||
to*: Address
|
||||
data*: seq[byte]
|
||||
value*: UInt256
|
||||
nonce*: ?UInt256
|
||||
chainId*: ?UInt256
|
||||
gasPrice*: ?UInt256
|
||||
maxFee*: ?UInt256
|
||||
maxPriorityFee*: ?UInt256
|
||||
gasLimit*: ?UInt256
|
||||
transactionType* {.serialize("type").}: ?TransactionType
|
||||
|
||||
func `$`*(transaction: Transaction): string =
|
||||
result = "("
|
||||
if sender =? transaction.`from`:
|
||||
if sender =? transaction.sender:
|
||||
result &= "from: " & $sender & ", "
|
||||
result &= "to: " & $transaction.to & ", "
|
||||
result &= "value: " & $transaction.value & ", "
|
||||
@ -35,6 +35,6 @@ func `$`*(transaction: Transaction): string =
|
||||
result &= ", gasPrice: " & $gasPrice
|
||||
if gasLimit =? transaction.gasLimit:
|
||||
result &= ", gasLimit: " & $gasLimit
|
||||
if txType =? transaction.`type`:
|
||||
if txType =? transaction.transactionType:
|
||||
result &= ", type: " & $txType
|
||||
result &= ")"
|
||||
|
||||
@ -4,6 +4,7 @@ import pkg/ethers/provider
|
||||
import pkg/ethers/providers/jsonrpc/conversions
|
||||
import pkg/questionable
|
||||
import pkg/questionable/results
|
||||
import pkg/serde
|
||||
import pkg/stew/byteutils
|
||||
|
||||
func flatten(s: string): string =
|
||||
@ -137,7 +138,7 @@ suite "JSON Conversions":
|
||||
fail
|
||||
check tx.blockHash == BlockHash(array[32, byte].fromHex("0x595bffbe897e025ea2df3213c4cc52c3f3d69bc04b49011d558f1b0e70038922"))
|
||||
check tx.blockNumber == 0x22e.u256
|
||||
check tx.`from` == Address.init("0xe00b677c29ff8d8fe6068530e2bc36158c54dd34").get
|
||||
check tx.sender == Address.init("0xe00b677c29ff8d8fe6068530e2bc36158c54dd34").get
|
||||
check tx.gas == 0x4d4bb.u256
|
||||
check tx.gasPrice == 0x3b9aca07.u256
|
||||
check tx.hash == TransactionHash(array[32, byte].fromHex("0xa31608907c338d6497b0c6ec81049d845c7d409490ebf78171f35143897ca790"))
|
||||
@ -146,7 +147,7 @@ suite "JSON Conversions":
|
||||
check tx.to == Address.init("0x92f09aa59dccb892a9f5406ddd9c0b98f02ea57e").get
|
||||
check tx.transactionIndex == 0x3.u256
|
||||
check tx.value == 0.u256
|
||||
check tx.`type` == some TransactionType.Legacy
|
||||
check tx.transactionType == some TransactionType.Legacy
|
||||
check tx.chainId == some 0xc0de4.u256
|
||||
check tx.v == 0x181bec.u256
|
||||
check tx.r == UInt256.fromBytesBE(hexToSeqByte("0x57ba18460934526333b80b0fea08737c363f3cd5fbec4a25a8a25e3e8acb362a"))
|
||||
@ -156,7 +157,7 @@ suite "JSON Conversions":
|
||||
let tx = PastTransaction(
|
||||
blockHash: BlockHash(array[32, byte].fromHex("0x595bffbe897e025ea2df3213c4cc52c3f3d69bc04b49011d558f1b0e70038922")),
|
||||
blockNumber: 0x22e.u256,
|
||||
`from`: Address.init("0xe00b677c29ff8d8fe6068530e2bc36158c54dd34").get,
|
||||
sender: Address.init("0xe00b677c29ff8d8fe6068530e2bc36158c54dd34").get,
|
||||
gas: 0x4d4bb.u256,
|
||||
gasPrice: 0x3b9aca07.u256,
|
||||
hash: TransactionHash(array[32, byte].fromHex("0xa31608907c338d6497b0c6ec81049d845c7d409490ebf78171f35143897ca790")),
|
||||
@ -168,7 +169,7 @@ suite "JSON Conversions":
|
||||
v: 0x181bec.u256,
|
||||
r: UInt256.fromBytesBE(hexToSeqByte("0x57ba18460934526333b80b0fea08737c363f3cd5fbec4a25a8a25e3e8acb362a")),
|
||||
s: UInt256.fromBytesBE(hexToSeqByte("0x33aa50bc8bd719b6b17ad0bf52006bf8943999198f2bf731eb33c118091000f2")),
|
||||
`type`: some TransactionType.Legacy,
|
||||
transactionType: some TransactionType.Legacy,
|
||||
chainId: some 0xc0de4.u256
|
||||
)
|
||||
let expected = """
|
||||
|
||||
@ -52,7 +52,7 @@ suite "JsonRpcSigner":
|
||||
let signer = provider.getSigner()
|
||||
let transaction = Transaction.example
|
||||
let populated = await signer.populateTransaction(transaction)
|
||||
check !populated.`from` == await signer.getAddress()
|
||||
check !populated.sender == await signer.getAddress()
|
||||
check !populated.gasPrice == await signer.getGasPrice()
|
||||
check !populated.nonce == await signer.getTransactionCount(BlockTag.pending)
|
||||
check !populated.gasLimit == await signer.estimateGas(transaction)
|
||||
@ -61,7 +61,7 @@ suite "JsonRpcSigner":
|
||||
test "populate does not overwrite existing fields":
|
||||
let signer = provider.getSigner()
|
||||
var transaction = Transaction.example
|
||||
transaction.`from` = some await signer.getAddress()
|
||||
transaction.sender = some await signer.getAddress()
|
||||
transaction.nonce = some UInt256.example
|
||||
transaction.chainId = some await signer.getChainId()
|
||||
transaction.gasPrice = some UInt256.example
|
||||
@ -72,7 +72,7 @@ suite "JsonRpcSigner":
|
||||
test "populate fails when sender does not match signer address":
|
||||
let signer = provider.getSigner()
|
||||
var transaction = Transaction.example
|
||||
transaction.`from` = accounts[1].some
|
||||
transaction.sender = accounts[1].some
|
||||
expect SignerError:
|
||||
discard await signer.populateTransaction(transaction)
|
||||
|
||||
|
||||
@ -1,498 +0,0 @@
|
||||
import std/math
|
||||
import std/options
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
import std/unittest
|
||||
import pkg/stew/byteutils
|
||||
import pkg/stint
|
||||
import pkg/ethers/providers/jsonrpc/json as utilsjson
|
||||
import pkg/questionable
|
||||
import pkg/questionable/results
|
||||
|
||||
|
||||
func flatten(s: string): string =
|
||||
s.replace(" ")
|
||||
.replace("\n")
|
||||
|
||||
suite "json serialization - serialize":
|
||||
|
||||
test "serializes UInt256 to non-hex string representation":
|
||||
check (% 100000.u256) == newJString("100000")
|
||||
|
||||
test "serializes sequence to an array":
|
||||
let json = % @[1, 2, 3]
|
||||
let expected = "[1,2,3]"
|
||||
check $json == expected
|
||||
|
||||
test "serializes Option[T] when has a value":
|
||||
let obj = %(some 1)
|
||||
let expected = "1"
|
||||
check $obj == expected
|
||||
|
||||
test "serializes Option[T] when doesn't have a value":
|
||||
let obj = %(none int)
|
||||
let expected = "null"
|
||||
check $obj == expected
|
||||
|
||||
test "serializes uints int.high or smaller":
|
||||
let largeUInt: uint = uint(int.high)
|
||||
check %largeUInt == newJInt(BiggestInt(largeUInt))
|
||||
|
||||
test "serializes large uints":
|
||||
let largeUInt: uint = uint(int.high) + 1'u
|
||||
check %largeUInt == newJString($largeUInt)
|
||||
|
||||
test "serializes Inf float":
|
||||
check %Inf == newJString("inf")
|
||||
|
||||
test "serializes -Inf float":
|
||||
check %(-Inf) == newJString("-inf")
|
||||
|
||||
test "can construct json objects with %*":
|
||||
type MyObj = object
|
||||
mystring {.serialize.}: string
|
||||
myint {.serialize.}: int
|
||||
myoption {.serialize.}: ?bool
|
||||
|
||||
let myobj = MyObj(mystring: "abc", myint: 123, myoption: some true)
|
||||
let mystuint = 100000.u256
|
||||
|
||||
let json = %*{
|
||||
"myobj": myobj,
|
||||
"mystuint": mystuint
|
||||
}
|
||||
|
||||
let expected = """{
|
||||
"myobj": {
|
||||
"mystring": "abc",
|
||||
"myint": 123,
|
||||
"myoption": true
|
||||
},
|
||||
"mystuint": "100000"
|
||||
}""".flatten
|
||||
|
||||
check $json == expected
|
||||
|
||||
test "only serializes marked fields":
|
||||
type MyObj = object
|
||||
mystring {.serialize.}: string
|
||||
myint {.serialize.}: int
|
||||
mybool: bool
|
||||
|
||||
let obj = % MyObj(mystring: "abc", myint: 1, mybool: true)
|
||||
|
||||
let expected = """{
|
||||
"mystring": "abc",
|
||||
"myint": 1
|
||||
}""".flatten
|
||||
|
||||
check $obj == expected
|
||||
|
||||
test "serializes ref objects":
|
||||
type MyRef = ref object
|
||||
mystring {.serialize.}: string
|
||||
myint {.serialize.}: int
|
||||
|
||||
let obj = % MyRef(mystring: "abc", myint: 1)
|
||||
|
||||
let expected = """{
|
||||
"mystring": "abc",
|
||||
"myint": 1
|
||||
}""".flatten
|
||||
|
||||
check $obj == expected
|
||||
|
||||
suite "json serialization - deserialize":
|
||||
|
||||
test "deserializes NaN float":
|
||||
check %NaN == newJString("nan")
|
||||
|
||||
test "deserialize enum":
|
||||
type MyEnum = enum
|
||||
First,
|
||||
Second
|
||||
let json = newJString("Second")
|
||||
check !MyEnum.fromJson(json) == Second
|
||||
|
||||
test "deserializes UInt256 from non-hex string representation":
|
||||
let json = newJString("100000")
|
||||
check !UInt256.fromJson(json) == 100000.u256
|
||||
|
||||
test "deserializes Option[T] when has a value":
|
||||
let json = newJInt(1)
|
||||
check (!fromJson(?int, json) == some 1)
|
||||
|
||||
test "deserializes Option[T] when doesn't have a value":
|
||||
let json = newJNull()
|
||||
check !fromJson(?int, json) == none int
|
||||
|
||||
test "deserializes float":
|
||||
let json = newJFloat(1.234)
|
||||
check !float.fromJson(json) == 1.234
|
||||
|
||||
test "deserializes Inf float":
|
||||
let json = newJString("inf")
|
||||
check !float.fromJson(json) == Inf
|
||||
|
||||
test "deserializes -Inf float":
|
||||
let json = newJString("-inf")
|
||||
check !float.fromJson(json) == -Inf
|
||||
|
||||
test "deserializes NaN float":
|
||||
let json = newJString("nan")
|
||||
check (!float.fromJson(json)).isNaN
|
||||
|
||||
test "deserializes array to sequence":
|
||||
let expected = @[1, 2, 3]
|
||||
let json = !"[1,2,3]".parseJson
|
||||
check !seq[int].fromJson(json) == expected
|
||||
|
||||
test "deserializes uints int.high or smaller":
|
||||
let largeUInt: uint = uint(int.high)
|
||||
let json = newJInt(BiggestInt(largeUInt))
|
||||
check !uint.fromJson(json) == largeUInt
|
||||
|
||||
test "deserializes large uints":
|
||||
let largeUInt: uint = uint(int.high) + 1'u
|
||||
let json = newJString($BiggestUInt(largeUInt))
|
||||
check !uint.fromJson(json) == largeUInt
|
||||
|
||||
test "can deserialize json objects":
|
||||
type MyObj = object
|
||||
mystring: string
|
||||
myint: int
|
||||
myoption: ?bool
|
||||
|
||||
let expected = MyObj(mystring: "abc", myint: 123, myoption: some true)
|
||||
|
||||
let json = !parseJson("""{
|
||||
"mystring": "abc",
|
||||
"myint": 123,
|
||||
"myoption": true
|
||||
}""")
|
||||
check !MyObj.fromJson(json) == expected
|
||||
|
||||
test "ignores serialize pragma when deserializing":
|
||||
type MyObj = object
|
||||
mystring {.serialize.}: string
|
||||
mybool: bool
|
||||
|
||||
let expected = MyObj(mystring: "abc", mybool: true)
|
||||
|
||||
let json = !parseJson("""{
|
||||
"mystring": "abc",
|
||||
"mybool": true
|
||||
}""")
|
||||
|
||||
check !MyObj.fromJson(json) == expected
|
||||
|
||||
test "deserializes objects with extra fields":
|
||||
type MyObj = object
|
||||
mystring: string
|
||||
mybool: bool
|
||||
|
||||
let expected = MyObj(mystring: "abc", mybool: true)
|
||||
|
||||
let json = !"""{
|
||||
"mystring": "abc",
|
||||
"mybool": true,
|
||||
"extra": "extra"
|
||||
}""".parseJson
|
||||
check !MyObj.fromJson(json) == expected
|
||||
|
||||
test "deserializes objects with less fields":
|
||||
type MyObj = object
|
||||
mystring: string
|
||||
mybool: bool
|
||||
|
||||
let expected = MyObj(mystring: "abc", mybool: false)
|
||||
|
||||
let json = !"""{
|
||||
"mystring": "abc"
|
||||
}""".parseJson
|
||||
check !MyObj.fromJson(json) == expected
|
||||
|
||||
test "deserializes ref objects":
|
||||
type MyRef = ref object
|
||||
mystring: string
|
||||
myint: int
|
||||
|
||||
let expected = MyRef(mystring: "abc", myint: 1)
|
||||
|
||||
let json = !"""{
|
||||
"mystring": "abc",
|
||||
"myint": 1
|
||||
}""".parseJson
|
||||
|
||||
let deserialized = !MyRef.fromJson(json)
|
||||
check deserialized.mystring == expected.mystring
|
||||
check deserialized.myint == expected.myint
|
||||
|
||||
|
||||
suite "json serialization pragmas":
|
||||
|
||||
test "fails to compile when object marked with 'serialize' specifies options":
|
||||
type
|
||||
MyObj {.serialize(key="test", ignore=true).} = object
|
||||
|
||||
check not compiles(%MyObj())
|
||||
|
||||
test "compiles when object marked with 'serialize' only":
|
||||
type
|
||||
MyObj {.serialize.} = object
|
||||
|
||||
check compiles(%MyObj())
|
||||
|
||||
test "fails to compile when field marked with 'deserialize' specifies mode":
|
||||
type
|
||||
MyObj = object
|
||||
field {.deserialize(mode=OptIn).}: bool
|
||||
|
||||
check not compiles(MyObj.fromJson("""{"field":true}"""))
|
||||
|
||||
test "compiles when object marked with 'deserialize' specifies mode":
|
||||
type
|
||||
MyObj {.deserialize(mode=OptIn).} = object
|
||||
field: bool
|
||||
|
||||
check compiles(MyObj.fromJson("""{"field":true}"""))
|
||||
|
||||
test "fails to compile when object marked with 'deserialize' specifies key":
|
||||
type
|
||||
MyObj {.deserialize("test").} = object
|
||||
field: bool
|
||||
|
||||
check not compiles(MyObj.fromJson("""{"field":true}"""))
|
||||
|
||||
test "compiles when field marked with 'deserialize' specifies key":
|
||||
type
|
||||
MyObj = object
|
||||
field {.deserialize("test").}: bool
|
||||
|
||||
check compiles(MyObj.fromJson("""{"field":true}"""))
|
||||
|
||||
test "compiles when field marked with empty 'deserialize'":
|
||||
type
|
||||
MyObj = object
|
||||
field {.deserialize.}: bool
|
||||
|
||||
check compiles(MyObj.fromJson("""{"field":true}"""))
|
||||
|
||||
test "compiles when field marked with 'serialize'":
|
||||
type
|
||||
MyObj = object
|
||||
field {.serialize.}: bool
|
||||
|
||||
check compiles(%MyObj())
|
||||
|
||||
|
||||
suite "json serialization, mode = OptIn":
|
||||
|
||||
test "serializes with default mode OptIn when object not marked with serialize":
|
||||
type MyObj = object
|
||||
field1 {.serialize.}: bool
|
||||
field2: bool
|
||||
|
||||
let obj = MyObj(field1: true, field2: true)
|
||||
check obj.toJson == """{"field1":true}"""
|
||||
|
||||
test "not marking object with serialize is equivalent to marking it with serialize in OptIn mode":
|
||||
type MyObj = object
|
||||
field1 {.serialize.}: bool
|
||||
field2: bool
|
||||
|
||||
type MyObjMarked {.serialize(mode=OptIn).} = object
|
||||
field1 {.serialize.}: bool
|
||||
field2: bool
|
||||
|
||||
let obj = MyObj(field1: true, field2: true)
|
||||
let objMarked = MyObjMarked(field1: true, field2: true)
|
||||
check obj.toJson == objMarked.toJson
|
||||
|
||||
test "serializes field with key when specified":
|
||||
type MyObj = object
|
||||
field1 {.serialize("test").}: bool
|
||||
field2 {.serialize.}: bool
|
||||
|
||||
let obj = MyObj(field1: true, field2: true)
|
||||
check obj.toJson == """{"test":true,"field2":true}"""
|
||||
|
||||
test "does not serialize ignored field":
|
||||
type MyObj = object
|
||||
field1 {.serialize.}: bool
|
||||
field2 {.serialize(ignore=true).}: bool
|
||||
|
||||
let obj = MyObj(field1: true, field2: true)
|
||||
check obj.toJson == """{"field1":true}"""
|
||||
|
||||
|
||||
suite "json deserialization, mode = OptIn":
|
||||
|
||||
test "deserializes only fields marked as deserialize when mode is OptIn":
|
||||
type MyObj {.deserialize(mode=OptIn).} = object
|
||||
field1: int
|
||||
field2 {.deserialize.}: bool
|
||||
|
||||
let val = !MyObj.fromJson("""{"field1":true,"field2":true}""")
|
||||
check val == MyObj(field1: 0, field2: true)
|
||||
|
||||
test "deserializes Optional fields when mode is OptIn":
|
||||
type MyObj {.deserialize(mode=OptIn).} = object
|
||||
field1 {.deserialize.}: bool
|
||||
field2 {.deserialize.}: Option[bool]
|
||||
|
||||
let val = !MyObj.fromJson("""{"field1":true}""")
|
||||
check val == MyObj(field1: true, field2: none bool)
|
||||
|
||||
|
||||
suite "json serialization, mode = OptOut":
|
||||
|
||||
test "serialize on object definition defaults to OptOut mode, serializes all fields":
|
||||
type MyObj {.serialize.} = object
|
||||
field1: bool
|
||||
field2: bool
|
||||
|
||||
let obj = MyObj(field1: true, field2: true)
|
||||
check obj.toJson == """{"field1":true,"field2":true}"""
|
||||
|
||||
test "not specifying serialize mode is equivalent to specifying OptOut mode":
|
||||
type MyObj {.serialize.} = object
|
||||
field1: bool
|
||||
field2: bool
|
||||
|
||||
type MyObjMarked {.serialize(mode=OptOut).} = object
|
||||
field1: bool
|
||||
field2: bool
|
||||
|
||||
let obj = MyObj(field1: true, field2: true)
|
||||
let objMarked = MyObjMarked(field1: true, field2: true)
|
||||
check obj.toJson == objMarked.toJson
|
||||
|
||||
test "ignores field when marked with ignore":
|
||||
type MyObj {.serialize.} = object
|
||||
field1 {.serialize(ignore=true).}: bool
|
||||
field2: bool
|
||||
|
||||
let obj = MyObj(field1: true, field2: true)
|
||||
check obj.toJson == """{"field2":true}"""
|
||||
|
||||
test "serializes field with key instead of field name":
|
||||
type MyObj {.serialize.} = object
|
||||
field1 {.serialize("test").}: bool
|
||||
field2: bool
|
||||
|
||||
let obj = MyObj(field1: true, field2: true)
|
||||
check obj.toJson == """{"test":true,"field2":true}"""
|
||||
|
||||
|
||||
suite "json deserialization, mode = OptOut":
|
||||
|
||||
test "deserializes object in OptOut mode when not marked with deserialize":
|
||||
type MyObj = object
|
||||
field1: bool
|
||||
field2: bool
|
||||
|
||||
let val = !MyObj.fromJson("""{"field1":true,"field3":true}""")
|
||||
check val == MyObj(field1: true, field2: false)
|
||||
|
||||
test "deserializes object field with marked json key":
|
||||
type MyObj = object
|
||||
field1 {.deserialize("test").}: bool
|
||||
field2: bool
|
||||
|
||||
let val = !MyObj.fromJson("""{"test":true,"field2":true}""")
|
||||
check val == MyObj(field1: true, field2: true)
|
||||
|
||||
test "fails to deserialize object field with wrong type":
|
||||
type MyObj = object
|
||||
field1: int
|
||||
field2: bool
|
||||
|
||||
let r = MyObj.fromJson("""{"field1":true,"field2":true}""")
|
||||
check r.isFailure
|
||||
check r.error of UnexpectedKindError
|
||||
check r.error.msg == "deserialization to int failed: expected {JInt} but got JBool"
|
||||
|
||||
test "does not deserialize ignored fields in OptOut mode":
|
||||
type MyObj = object
|
||||
field1 {.deserialize(ignore=true).}: bool
|
||||
field2: bool
|
||||
|
||||
let val = !MyObj.fromJson("""{"field1":true,"field2":true}""")
|
||||
check val == MyObj(field1: false, field2: true)
|
||||
|
||||
test "deserializes fields when marked with deserialize but not ignored":
|
||||
type MyObj = object
|
||||
field1 {.deserialize.}: bool
|
||||
field2: bool
|
||||
|
||||
let val = !MyObj.fromJson("""{"field1":true,"field2":true}""")
|
||||
check val == MyObj(field1: true, field2: true)
|
||||
|
||||
test "deserializes Optional field":
|
||||
type MyObj = object
|
||||
field1: Option[bool]
|
||||
field2: bool
|
||||
|
||||
let val = !MyObj.fromJson("""{"field2":true}""")
|
||||
check val == MyObj(field1: none bool, field2: true)
|
||||
|
||||
|
||||
suite "json serialization - mode = Strict":
|
||||
|
||||
test "serializes all fields in Strict mode":
|
||||
type MyObj {.serialize(mode=Strict).} = object
|
||||
field1: bool
|
||||
field2: bool
|
||||
|
||||
let obj = MyObj(field1: true, field2: true)
|
||||
check obj.toJson == """{"field1":true,"field2":true}"""
|
||||
|
||||
test "ignores ignored fields in Strict mode":
|
||||
type MyObj {.serialize(mode=Strict).} = object
|
||||
field1 {.serialize(ignore=true).}: bool
|
||||
field2: bool
|
||||
|
||||
let obj = MyObj(field1: true, field2: true)
|
||||
check obj.toJson == """{"field1":true,"field2":true}"""
|
||||
|
||||
|
||||
suite "json deserialization, mode = Strict":
|
||||
|
||||
test "deserializes matching object and json fields when mode is Strict":
|
||||
type MyObj {.deserialize(mode=Strict).} = object
|
||||
field1: bool
|
||||
field2: bool
|
||||
|
||||
let val = !MyObj.fromJson("""{"field1":true,"field2":true}""")
|
||||
check val == MyObj(field1: true, field2: true)
|
||||
|
||||
test "fails to deserialize with missing json field when mode is Strict":
|
||||
type MyObj {.deserialize(mode=Strict).} = object
|
||||
field1: bool
|
||||
field2: bool
|
||||
|
||||
let r = MyObj.fromJson("""{"field2":true}""")
|
||||
check r.isFailure
|
||||
check r.error of SerdeError
|
||||
check r.error.msg == "object field missing in json: field1"
|
||||
|
||||
test "fails to deserialize with missing object field when mode is Strict":
|
||||
type MyObj {.deserialize(mode=Strict).} = object
|
||||
field2: bool
|
||||
|
||||
let r = MyObj.fromJson("""{"field1":true,"field2":true}""")
|
||||
check r.isFailure
|
||||
check r.error of SerdeError
|
||||
check r.error.msg == "json field(s) missing in object: {\"field1\"}"
|
||||
|
||||
test "deserializes ignored fields in Strict mode":
|
||||
type MyObj {.deserialize(mode=Strict).} = object
|
||||
field1 {.deserialize(ignore=true).}: bool
|
||||
field2: bool
|
||||
|
||||
let val = !MyObj.fromJson("""{"field1":true,"field2":true}""")
|
||||
check val == MyObj(field1: true, field2: true)
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user