mirror of
https://github.com/logos-messaging/logos-messaging-nim.git
synced 2026-07-10 02:09:31 +00:00
* 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
156 lines
5.7 KiB
Nim
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.}
|