let lib = import ; allowedEndpointKeys = [ "type" "listenPort" "content" ]; allowedContentKeys = [ "host" "port" ]; ensureNoUnknownKeys = context: obj: allowedKeys: let unknown = builtins.filter (key: !(lib.elem key allowedKeys)) (builtins.attrNames obj); in if unknown != [] then throw "${context} contains unknown keys: ${builtins.concatStringsSep ", " unknown}" else obj; ensurePort = value: builtins.isInt value && value > 0 && value <= 65535; validateEndpointShape = index: endpoint: let _ = if builtins.isAttrs endpoint then null else throw "Endpoint at index ${toString index} must be an attrset."; __ = ensureNoUnknownKeys "Endpoint at index ${toString index}" endpoint allowedEndpointKeys; typeValue = if endpoint ? type && builtins.isString endpoint.type then endpoint.type else throw "Endpoint at index ${toString index} must define type as a string."; _type = if lib.elem typeValue [ "forwarding" "proxy" ] then null else throw "Endpoint at index ${toString index} type must be 'forwarding' or 'proxy'."; _listenPort = if endpoint ? listenPort && ensurePort endpoint.listenPort then null else throw "Endpoint at index ${toString index} must define listenPort as int in range 1..65535."; contentValue = if endpoint ? content then endpoint.content else throw "Endpoint at index ${toString index} must define content."; _content = let _attrs = if builtins.isAttrs contentValue then null else throw "Endpoint at index ${toString index} must define content as an attrset."; ____ = ensureNoUnknownKeys "Endpoint content at index ${toString index}" contentValue allowedContentKeys; in if !(contentValue ? port) || !ensurePort contentValue.port then throw "Endpoint at index ${toString index} must define content.port as int in range 1..65535." else if typeValue == "proxy" && (!(contentValue ? host) || !builtins.isString contentValue.host || contentValue.host == "") then throw "Proxy endpoint at index ${toString index} must define content.host as non-empty string." else if typeValue == "forwarding" && contentValue ? host then throw "Forwarding endpoint at index ${toString index} must not define content.host." else null; in endpoint; validateEndpointsShape = endpoints: if !builtins.isList endpoints then throw "config/endpoints.nix must evaluate to a list of endpoint attrsets." else lib.imap0 validateEndpointShape endpoints; validateUniqueHostPath = endpoints: let keyFor = endpoint: toString endpoint.listenPort; keys = map keyFor endpoints; in if builtins.length keys == builtins.length (lib.unique keys) then endpoints else throw "Duplicate listenPort detected in config/endpoints.nix."; in { getValidatedEndpoints = endpoints: validateUniqueHostPath (validateEndpointsShape endpoints); }