moonknob

    Which of two settings wins, and what to do when they disagree: the precedence every moonbitstack library shares. Not a configuration loader; that is moonconf.

    moonbit
    configuration
    config
    settings
    options
    parameters
    precedence
    priority
    override
    defaults
    merge
    union
    conflict
    clash
    resolution
    policy
    Download zip
    Version
    0.2.0
    License
    Apache-2.0
    Last updated
    12 hours ago
    Downloads
    693

    No dependencies

    #moonknob

    Which of two settings wins, and what to do when they disagree. A function that takes a settings record usually takes a few flat arguments beside it, one per setting a caller changes often; this is the one place that decides which of the two wins and what is said about it.

    library preset < the record passed in < this call's argument

    The more specific a setting, the later it applies. Two parameters change that, and each function documents the default it chose:

    • wins is Extra (the argument wins) or Base (the record wins). Base is for a function whose starting point is an authority, such as the header a signature is about to be made under.
    • clash says what happens when a setting is given twice with different values.

    clashwhat the call answerswhat it says
    Ignorewhat wins chosenothing
    Warn(f)what wins choseone line to f
    Panicnothing: the program stopsthe message
    Handle(f)what f returnsf gets the names, the base and the merge

    The same value given twice is not a clash, and nothing is reported for it. With no clash at all, settle answers merged whatever the policy is.

    #A function that takes a record

    struct Options {
    timeout : Int32
    retries : Int32
    }

    let options : Options = { timeout: 30, retries: 3 }

    fn connect(
    options~ : Options = options,
    timeout? : Int32,
    retries? : Int32,
    wins~ : @moonknob.Wins = Extra,
    clash~ : @moonknob.OnClash[Options] = Ignore,
    ) -> Options {
    let merge = @moonknob.Merge::new(wins)
    let merged = {
    timeout: merge.pick("timeout", options.timeout, timeout),
    retries: merge.pick("retries", options.retries, retries),
    }
    merge.settle(clash, options, merged, "connect: options and arguments")
    }

    pick takes one setting: its name, the record's value, and what the caller gave, which is None when it gave nothing. settle applies the policy to the names that were gathered; site names the call and its two sources in the warning and in the abort.

    #Calling it

    let mine = { timeout: 5, retries: 1, }
    let by_default = connect(options=mine, timeout=10)
    let by_record = connect(options=mine, timeout=10, wins=Base)

    assert_eq(by_default.timeout, 10)
    assert_eq(by_record.timeout, 5)

    Being told instead of guessing:

    let said : Array[String] = []
    let lines = connect(options=mine, timeout=10, clash=Warn(line => said.push(line)))
    assert_eq(lines.timeout, 10)
    assert_eq(said, ["connect: options and arguments: timeout given twice with different values"])

    Handle gets a Clash — the names, the base and the merge — and answers with what the program should use:

    let keep_retries : @moonknob.OnClash[Options] = Handle(sides => sides.base)
    assert_eq(connect(options=mine, timeout=10, clash=keep_retries).timeout, 5)

    The library never prints: Warn takes a function, so the line goes wherever the program writes.

    #Two maps

    let merge = @moonknob.Merge::new(Extra)
    let base = { "alg": "none", "typ": "JWT" }
    let extra = { "typ": "JOSE", "kid": "k1" }
    let heads = merge.union(base, extra)
    assert_eq(heads.to_array(), [
    ("alg", "none"),
    ("typ", "JOSE"),
    ("kid", "k1"),
    ])

    Key by key under the same two knobs, with the keys of the first side in their order and then the new ones of the second.

    #Licence

    Apache-2.0.

    Clash

    pub(all) struct Clash[T] {
    names : Array[String]
    base : T
    merged : T
    } derive(Eq,
    Debug
    )

    Settings given twice with different values, as Handle receives them.

    Clash::equal

    fn[T : Eq] Clash::equal(Clash[T], Clash[T]) -> Bool

    Clash::not_equal

    fn[T : Eq] Clash::not_equal(x : Clash[T], y : Clash[T]) -> Bool

    Merge

    type Merge

    Lays what a caller gave over what a function starts from, one setting at a time, and keeps the names of those that disagreed for settle.

    test {
    let merge = @moonknob.Merge::new(Extra)
    let timeout = merge.pick("timeout", 30, Some(10))
    assert_eq(timeout, 10)
    assert_eq(merge.names().to_owned(), ["timeout"])
    }

    Merge::names

    fn Merge::names(self : Merge) -> ArrayView[String]

    The names of the settings that disagreed so far.

    Merge::new

    fn Merge::new(wins : Wins) -> Merge

    A merge that has recorded nothing yet and takes wins for every setting it is handed from here on.

    Merge::pick

    fn[V : Eq] Merge::pick(self : Merge, name : String, base : V, given : V?) -> V

    One setting. given is None when the caller did not give it, and then base stands; a value equal to base stands without being a clash.

    Merge::settle

    fn[T] Merge::settle(self : Merge, clash : OnClash[T], base : T, merged : T, site : String) -> T

    clash applied to what was gathered: see [OnClash::settle].

    Merge::union

    fn[V : Eq] Merge::union(self : Merge, base : Map[String, V], extra : Map[String, V]) -> Map[String, V]

    Two maps as one: every key of both, in the order base then extra has them, with wins deciding a key they both hold with different values.

    OnClash

    pub(all) enum OnClash[T] {
    Ignore
    Warn((String) -> Unit)
    Panic
    Handle((Clash[T]) -> T)
    }

    What to do when a setting is given twice with different values.

    The same value given twice is not a clash: there is only one answer, and nothing is reported.

    OnClash::settle

    fn[T] OnClash::settle(self : OnClash[T], base : T, merged : T, names : Array[String], site : String) -> T

    What a function ends up with, given what it started from, the two laid together, and the settings that disagreed. With no names there was no clash, and merged comes back whatever self is.

    site is the call and its two sources, for the warning and the abort: "jwt.verify: policy and arguments".

    Wins

    pub(all) enum Wins {
    Base
    Extra
    } derive(Eq,
    Debug
    )

    Which side wins when a setting is given twice: once in what a function starts from, once in what the caller lays over it.

    A function that takes a configuration record usually takes flat arguments beside it, one per setting a caller changes often, and the more specific of the two is applied last: library preset, then the record, then this call's arguments. That is Extra. Base turns it round for a function whose starting point is an authority rather than a default, such as the header a signature is about to be made under.

    Wins::equal

    fn Wins::equal(Wins, Wins) -> Bool

    Wins::not_equal

    fn Wins::not_equal(x : Wins, y : Wins) -> Bool

    Wins::to_repr