diff --git a/flake.nix b/flake.nix index cb826bb..b056eb1 100644 --- a/flake.nix +++ b/flake.nix @@ -179,7 +179,28 @@ }; }; eitherN = tn: - typedef "either<${concatStringsSep ", " (map (x: x.name) tn)}>" (x: any (t: (self.type t).check x) tn); + let + combine = a: b: if a == null then b else "[${a}]: ${b}"; + logContexts = map (t: t.logContext or null) tn; + # Function to pair two lists, combining their corresponding elements + pairList = listA: listB: lib.lists.zipListsWith combine listA listB; + in + _typedef' null rec { + name = "either<${concatStringsSep ", " (map (x: x.name) tn)}>"; + checkType = x: let + results = map (t: t.checkType x) tn; + allErrors = filter (r: !r.ok) results; + errorMsgs = map (r: r.err) allErrors; + formatError = err: "expected type '${name}', but found:\n" + err; + in + if any (r: r.ok) results + then { ok = true; } + else { + ok = false; + err = concatStringsSep "\n" (pairList logContexts (map formatError errorMsgs)); + }; + }; + either = t1: t2: self.eitherN [t1 t2]; list = t: typedef' rec { @@ -211,7 +232,7 @@ # Anonymous structs are supported (e.g. for nesting) by omitting the # name. # - inherit (import ./struct.nix {inherit self typedef' typeError;}) struct openStruct; + inherit (import ./struct.nix {inherit self typedef' typeError;}) struct openStruct structOption openStructOption; enum = let plain = name: def: diff --git a/struct.nix b/struct.nix index 35c86a7..d47017e 100644 --- a/struct.nix +++ b/struct.nix @@ -1,4 +1,8 @@ -{self, typedef', typeError}: +{ + self, + typedef', + typeError, +}: # Struct checking is more involved than the simpler types above. # To make the actual type definition more readable, several # helpers are defined below. @@ -73,6 +77,66 @@ with builtins; let else "" ); }; + + # Modified checkStruct that takes into account the ignored flag + checkStruct' = ignored: def: value: let + init = { + ok = true; + err = ""; + }; + # Function to check if a field should be ignored + shouldIgnore = n: ignored || hasAttr n def; + # Check fields based on the ignored flag + checkedFields = map ( + n: let + v = + if hasAttr n value + then value."${n}" + else null; + in + if shouldIgnore n + then + if hasAttr n def + then checkField def."${n}" n v + else { + ok = true; + err = ""; + } # Skip check if field is not in definition and ignored is true + else { + ok = false; + err = "field '${n}' is not among the types in [${concatStringsSep "," (attrNames def)}]\n"; + } # Fail check if ignored is false and field is not in definition + ) (attrNames value); + + combined = + foldl' ( + acc: res: { + ok = acc.ok && res.ok; + err = + if !res.ok + then acc.err + res.err + else acc.err; + } + ) + init + checkedFields; + in + combined; + + openStructCheck = name: ignored: def: + typedef' { + inherit name def; + checkType = value: + if isAttrs value + then (checkStruct' ignored (self.attrs self.type def) value) + else { + ok = false; + err = typeError name value; + }; + toError = _: result: + "expected '${name}'-struct, but found:\n" + result.err; + }; + struct' = name: isClosed: def: typedef' { inherit name def; @@ -96,4 +160,37 @@ in { if isString arg then struct' arg false else struct' "anon" false arg; + + /* + (structOption "option" {name = string; inputs = attrs any;}) {b = "s";} + => expected 'option'-struct, but found: + field 'b' is not among the types in [inputs,name] + + (structOption "option" {name = string; inputs = attrs any;}) {inputs = {};} + => + { Inputs = { ... }; } + */ + structOption = arg: + if isString arg + then openStructCheck arg false + else openStructCheck "anon" false arg; + + /* + (openStructOption "option" {name = string; inputs = attrs any;}) {b= "5";} + => + { b = "5"; } + + (openStructOption "option" {name = string; inputs = attrs any;}) {b= "5"; name = 5;} + => + expected 'option'-struct, but found: + field 'name': expected type 'string', but value '5' is of type 'int' + + (openStructOption "option" {name = string; inputs = attrs any;}) {b= "5"; name = "5";} + => + { b = "5"; name = "5"; } + */ + openStructOption = arg: + if isString arg + then openStructCheck arg true + else openStructCheck "anon" true arg; }