2025-12-13 22:01:54 +10:00
|
|
|
{
|
|
|
|
|
attrs,
|
|
|
|
|
result,
|
2025-12-14 10:13:04 +10:00
|
|
|
...
|
2025-12-13 22:01:54 +10:00
|
|
|
}: rec {
|
|
|
|
|
cmpStructErr' = errBadKeys: errBadValues: path: S: T:
|
2025-12-14 10:22:34 +10:00
|
|
|
if builtins.isAttrs S && builtins.isAttrs T
|
2025-12-13 22:01:54 +10:00
|
|
|
then let
|
|
|
|
|
keysS = builtins.attrNames S;
|
|
|
|
|
keysT = builtins.attrNames T;
|
|
|
|
|
in
|
|
|
|
|
# ensure all key names match, then recurse
|
|
|
|
|
if !(keysS == keysT)
|
|
|
|
|
then errBadKeys path keysS keysT
|
|
|
|
|
else
|
|
|
|
|
(result.firstErr
|
|
|
|
|
(builtins.map
|
|
|
|
|
(k: cmpStructErr' errBadKeys errBadValues (path ++ [k]) (keysS.${k}) (keysT.${k}))
|
|
|
|
|
keysS))
|
|
|
|
|
else
|
|
|
|
|
# terminating leaf in recursion tree reached
|
|
|
|
|
# ensure values' types match
|
|
|
|
|
(builtins.typeOf S == builtins.typeOf T)
|
|
|
|
|
|| errBadValues path S T;
|
|
|
|
|
|
|
|
|
|
cmpStructErr = errBadKeys: errBadValues: cmpStructErr' errBadKeys errBadValues [];
|
|
|
|
|
|
|
|
|
|
cmpStruct =
|
|
|
|
|
cmpStructErr
|
|
|
|
|
(path: keysS: keysT:
|
|
|
|
|
result.Err {
|
|
|
|
|
reason = "keys";
|
|
|
|
|
inherit path;
|
|
|
|
|
})
|
|
|
|
|
(path: S: T:
|
|
|
|
|
result.Ok "ok");
|
|
|
|
|
|
|
|
|
|
cmpTypedStruct =
|
|
|
|
|
cmpStructErr
|
|
|
|
|
(path: keysS: keysT:
|
|
|
|
|
result.Err {
|
|
|
|
|
reason = "keys";
|
|
|
|
|
inherit path;
|
|
|
|
|
})
|
|
|
|
|
(path: S: T:
|
|
|
|
|
result.Err {
|
|
|
|
|
reason = "values";
|
|
|
|
|
inherit path;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
# check is a function taking two structs
|
|
|
|
|
# and returning a result monad.
|
|
|
|
|
mergeStruct' = check: template: S: let
|
|
|
|
|
res = check template S;
|
|
|
|
|
in
|
|
|
|
|
result.errOr res ({...}:
|
|
|
|
|
attrs.mapAttrsRecursive (
|
|
|
|
|
path: value: let
|
|
|
|
|
valueS = attrs.attrValueAt S path;
|
|
|
|
|
in
|
|
|
|
|
if valueS != null
|
|
|
|
|
then valueS
|
|
|
|
|
else value
|
|
|
|
|
)
|
|
|
|
|
template);
|
|
|
|
|
|
|
|
|
|
mergeStruct = mergeStruct' (S: T: result.Ok "ok");
|
|
|
|
|
|
|
|
|
|
mergeTypedStruct = mergeStruct' (
|
|
|
|
|
cmpStructErr
|
|
|
|
|
(path: keysS: keysT:
|
|
|
|
|
result.Ok "ok")
|
|
|
|
|
(path: S: T:
|
|
|
|
|
result.Err {
|
|
|
|
|
reason = "values";
|
|
|
|
|
inherit path;
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
mergeStructStrict = mergeStruct' cmpStruct;
|
|
|
|
|
|
|
|
|
|
mergeTypedStructStrict = mergeStruct' cmpTypedStruct;
|
|
|
|
|
}
|