// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// What a reader will accept and what a writer will send.
///
/// Every default here is the Rust `fory` crate's `Config::default()`, because
/// that is the reference implementation the specification names first, and
/// because a default nobody else shares is a default an interop test finds.
/// Where this package has no counterpart for one of its knobs — the schema
/// version check, the type-meta cache — the knob is absent rather than inert.
pub(all) struct Config {
/// Whether the header byte carries the cross-language flag.
///
/// The xlang format is the one implemented here, so this is `true`; a writer
/// asked for `false` writes a header without the flag, and a reader asked for
/// `true` refuses one.
xlang : Bool
/// Whether values that are written more than once, or that take part in a
/// cycle, are sent once and pointed at afterwards.
///
/// Off by default, as in the Rust crate: reference tracking costs a byte per
/// tracked value and a table on both sides, which a graph without sharing
/// should not pay for. Turn it on and mark the shared nodes with `shared`.
track_ref : Bool
/// Whether the bytes of a string are checked as UTF-8 before they become a
/// `String`.
///
/// On by default, as in the Rust crate. Turning it off is a promise that the
/// peer is trusted, not a way to read a string that is not one.
check_string : Bool
/// How deeply values may nest before a message is refused.
///
/// Five is the Rust crate's `max_dyn_depth`. A message of ten thousand open
/// lists is a denial of service rather than a message, and the depth is the
/// one number that stops it before the stack does.
depth : Int32
/// How many fields a struct may declare before a message is refused.
///
/// Five hundred and twelve is the Rust crate's `max_type_fields`. The count
/// is written by the message and read before the fields are, so it is the
/// cheapest place to stop a message that claims more fields than it has.
fields : Int32
/// How many bytes of variable-length payload one message may declare before
/// it is refused: strings, binaries, dense arrays and container bodies added
/// up as they are read.
///
/// A hundred and twenty-eight mebibytes is the Rust crate's
/// `max_graph_memory_bytes`. Declared lengths are already bounded by the
/// bytes actually present, so this gate is the one that bounds a message
/// made of many small declarations whose total is a large allocation.
graph : Int32
}
///|
/// The preset every entry point takes when it is not given one.
///
/// Named `config` so that an argument reads `config=config` and an update reads
/// `{ ..config, track_ref: true }`.
pub let config : Config = {
xlang: true,
track_ref: false,
check_string: true,
depth: 5,
fields: 512,
graph: 134_217_728,
}
///|
/// The same preset, one argument at a time.
///
/// For when several parts differ at once and a record update would mean
/// spelling the ones that did not change.
pub fn Config::new(
xlang~ : Bool = config.xlang,
track_ref~ : Bool = config.track_ref,
check_string~ : Bool = config.check_string,
depth~ : Int32 = config.depth,
fields~ : Int32 = config.fields,
graph~ : Int32 = config.graph,
) -> Config {
{ xlang, track_ref, check_string, depth, fields, graph, }
}
///|
/// The configuration one call runs under.
///
/// The library preset is beaten by the record, and the record by this call's
/// flat mirrors, unless `wins` says otherwise; with no record, the preset
/// stands in for it. A flat mirror whose value differs from the record's is
/// what `clash` arbitrates: taken by `wins` silently or with a warning,
/// aborted on, or handed to the caller's function. Both are `moonknob`'s.
pub fn resolve(
config : Config?,
xlang : Bool?,
track_ref : Bool?,
check_string : Bool?,
depth : Int32?,
fields : Int32?,
graph : Int32?,
wins : @moonknob.Wins?,
clash : @moonknob.OnClash[Config]?,
) -> Config {
let base = match config {
Some(given) => given
None => Config::new()
}
let merge = @moonknob.Merge::new(wins.unwrap_or(Extra))
let merged = {
xlang: merge.pick("xlang", base.xlang, xlang),
track_ref: merge.pick("track_ref", base.track_ref, track_ref),
check_string: merge.pick("check_string", base.check_string, check_string),
depth: merge.pick("depth", base.depth, depth),
fields: merge.pick("fields", base.fields, fields),
graph: merge.pick("graph", base.graph, graph),
}
merge.settle(
clash.unwrap_or(Ignore),
base,
merged,
"moonfory: the config and the flat arguments",
)
}