logos-messaging-nim/tools/confutils/conf_from_json.nim
Fabiana Cecin 90fa5fa91f
feat: improve config v3 (#4015)
* remove --mode from the CLI
* move WakuMode to the messaging layer
* expose store backend (db url, max connections) and a remote store node on the messaging surface
* wakunode2 with no flags now runs as a full service node (store still opt-in)
* add rateLimitMessagesPerEpoch
* channel rate-limiting auto-enables if epochPeriodSec or messagesPerEpoch is set
* fix JSON conf parser to be generic (works over all config types)
* messaging config = mode + preset + messagingOverrides + channelsOverrides
* add full messaging plus selective kernel config options to MessagingClientConf
* mode (Core/Edge) expands to kernel protocol flags in the messaging layer
* create_node parses the messaging config, drops the flat WakuNodeConf JSON entrypoint
* wire channelsOverrides (segmentation/SDS/rate-limit) into channel creation
* fix liblogosdelivery.h comments and README for the new config shape
* messaging conf tests: switch names, reject-unknown, set-twice, field->kernel
* add kernel log-level, log-format, nodekey to the messaging surface
* Port 0 (ephemeral) default for messaging entry points
* KernelConf alias for WakuNodeConf
* rewrite the FFI examples to the new config shape
* C/C++ examples use preset status.prod
* drop operator-only confs from the examples
* remove duplicate tests & misc test fixes
* Delete p2pReliability from Kernel (Waku) resolver and config (keep preset definition)
* Delete NodeConfig API (deprecation completed by p2pReliability removal from kernel)
* Rename test_messaging_conf.nim to test_conf.nim (tests Logos Delivery config in general)
* Rename messaging_conf_json.nim to logos_delivery_conf_json.nim
* Add logos_delivery_conf.nim (defines LogosDeliveryConf aggregate)
* misc docs/comments cleanups
2026-07-09 12:21:41 -03:00

156 lines
5.7 KiB
Nim

import std/[json, macros, options, strutils, tables]
import confutils, confutils/defs, confutils/std/net, results
# The shared JSON walker is `raises: []` so the messaging FFI parser (also
# `raises: []`) can build on it.
{.push raises: [].}
proc collectJsonFields*(
jsonNode: JsonNode
): Result[Table[string, (string, JsonNode)], string] =
## Walk the top-level JSON object and key it by lowercased names.
if jsonNode.kind != JObject:
return err("config JSON must be a JSON object, got " & $jsonNode.kind)
var jsonFields: Table[string, (string, JsonNode)]
for key, value in jsonNode:
let lowerKey = key.toLowerAscii()
if jsonFields.hasKey(lowerKey):
let firstKey = jsonFields.getOrDefault(lowerKey)[0]
return err(
"Duplicate configuration option (case-insensitive): '" & firstKey & "' and '" &
key & "'"
)
jsonFields[lowerKey] = (key, value)
return ok(jsonFields)
proc unknownKeysError(
jsonFields: Table[string, (string, JsonNode)], prefix: string
): string =
## Format leftover JSON keys as an error message.
var keys = newSeq[string]()
for _, (jsonKey, _) in pairs(jsonFields):
keys.add(jsonKey)
return prefix & ": " & keys.join(", ")
proc jsonScalarToString(node: JsonNode): Result[string, string] =
## Convert a scalar JSON value to its string form.
case node.kind
of JString:
return ok(node.getStr())
of JInt:
return ok($node.getInt())
of JFloat:
return ok($node.getFloat())
of JBool:
return ok($node.getBool())
else:
return err("expected scalar JSON value, got " & $node.kind)
proc parseScalarInto[U](
jsonValue: JsonNode, confField, jsonKey, prefix: string
): Result[U, string] =
## Parse a scalar JSON value into `U` via confutils `parseCmdArg`, same as the CLI.
let s = jsonScalarToString(jsonValue).valueOr:
return
err(prefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & error)
try:
ok(parseCmdArg(U, s))
except CatchableError as e:
err(
prefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & e.msg &
". Value: " & s
)
proc parseSeqInto[U](
jsonValue: JsonNode, confField, jsonKey, prefix: string
): Result[seq[U], string] =
## Parse a JSON array into `seq[U]`, each element via `parseCmdArg`.
if jsonValue.kind != JArray:
return err(
prefix & " '" & confField & "' from JSON key '" & jsonKey &
"' must be a JSON array"
)
var res: seq[U]
for item in jsonValue:
let s = jsonScalarToString(item).valueOr:
return
err(prefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & error)
try:
res.add(parseCmdArg(U, s))
except CatchableError as e:
return err(
prefix & " '" & confField & "' from JSON key '" & jsonKey & "': " & e.msg &
". Value: " & s
)
ok(res)
proc applyJsonFieldsToConf*[T](
conf: var T,
jsonFields: var Table[string, (string, JsonNode)],
parseErrPrefix: string,
unknownErrPrefix: string,
): Result[void, string] =
## Write each conf field matched (case-insensitive) by name or CLI `name:` pragma.
## JSON `null` leaves the field unset; unknown or non-`parseCmdArg` keys error.
for confField, confValue in fieldPairs(conf):
# Match a field by its name or by its CLI name: pragma; case-insensitive.
var matchKey = ""
let lowerField = confField.toLowerAscii()
if jsonFields.hasKey(lowerField):
matchKey = lowerField
when confValue.hasCustomPragma(defs.name):
let lowerCliName = confValue.getCustomPragmaVal(defs.name).toLowerAscii()
if lowerCliName != lowerField and jsonFields.hasKey(lowerCliName):
if matchKey != "": # field-name form already present: set twice
return err(
"config option '" & confField & "' was set twice, via '" &
jsonFields.getOrDefault(matchKey)[0] & "' and '" &
jsonFields.getOrDefault(lowerCliName)[0] & "'"
)
matchKey = lowerCliName
if matchKey != "":
let (jsonKey, jsonValue) = jsonFields.getOrDefault(matchKey)
if jsonValue.kind == JNull:
# JSON null leaves the field unset; it keeps its default.
jsonFields.del(matchKey)
else:
when confValue is Option:
type Inner = typeof(confValue.get())
when Inner is seq:
type Elem = typeof(confValue.get()[0])
when compiles(parseCmdArg(Elem, "")):
confValue =
some(?parseSeqInto[Elem](jsonValue, confField, jsonKey, parseErrPrefix))
jsonFields.del(matchKey)
else:
return err("config option '" & jsonKey & "' cannot be set via JSON")
else:
when compiles(parseCmdArg(Inner, "")):
confValue = some(
?parseScalarInto[Inner](jsonValue, confField, jsonKey, parseErrPrefix)
)
jsonFields.del(matchKey)
else:
return err("config option '" & jsonKey & "' cannot be set via JSON")
elif confValue is seq:
type Elem = typeof(confValue[0])
when compiles(parseCmdArg(Elem, "")):
confValue =
?parseSeqInto[Elem](jsonValue, confField, jsonKey, parseErrPrefix)
jsonFields.del(matchKey)
else:
return err("config option '" & jsonKey & "' cannot be set via JSON")
else:
when compiles(parseCmdArg(typeof(confValue), "")):
confValue = ?parseScalarInto[typeof(confValue)](
jsonValue, confField, jsonKey, parseErrPrefix
)
jsonFields.del(matchKey)
else:
return err("config option '" & jsonKey & "' cannot be set via JSON")
if jsonFields.len > 0:
return err(unknownKeysError(jsonFields, unknownErrPrefix))
ok()
{.pop.}