feat: try rework
This commit is contained in:
parent
1ddbd3b8b6
commit
ecf10628c3
51 changed files with 1941 additions and 445 deletions
28
validation/README.md
Normal file
28
validation/README.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Validation Layer
|
||||
|
||||
This folder contains **shape/type validation only**.
|
||||
|
||||
## Rule of Responsibility
|
||||
- `validation/*`: syntax checks, required fields, allowed keys, and value types.
|
||||
- `intermediate/*` and service modules: semantic checks (contradictions/conflicts/business rules).
|
||||
|
||||
Examples:
|
||||
- Shape/type (validation): endpoint has `content` attrset, `port` is int, unknown keys are rejected.
|
||||
- Semantic (kept outside): `force_ssl = true` with `port = 80`, duplicate routes on same host key, incompatible TLS groupings.
|
||||
|
||||
## Files
|
||||
- `validation/endpoints.nix`
|
||||
: Validates endpoint schema and content schema for `proxy` and `web`.
|
||||
- `validation/auto_ssh.nix`
|
||||
: Validates `devices`/`auto_ssh` shapes and remote port map structure.
|
||||
- `validation/network_devices.nix`
|
||||
: Validates local device shapes and DHCP reservation field shapes used by intermediate DHCP/DNS models.
|
||||
- `validation/storage.nix`
|
||||
: Validates storage config entry shapes consumed by intermediate storage derivation.
|
||||
- `validation/secrets.nix`
|
||||
: Validates keystore entry/reference shapes consumed by config modules before service migration.
|
||||
- `validation/service/*`
|
||||
: Validates service-backed config shapes consumed by service modules, such as `kiwix`, `qbittorrent`, and `continuwuity`.
|
||||
|
||||
## Usage Pattern
|
||||
Import validators and run them first, then apply semantic checks locally.
|
||||
61
validation/auto_ssh.nix
Normal file
61
validation/auto_ssh.nix
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
let
|
||||
lib = import <nixpkgs/lib>;
|
||||
|
||||
getDevices = net:
|
||||
if net ? devices && builtins.isAttrs net.devices then
|
||||
net.devices
|
||||
else
|
||||
throw "config/network.nix must define devices as an attrset.";
|
||||
|
||||
getAutoSshDevices = devices:
|
||||
lib.filterAttrs (_: device:
|
||||
if !builtins.isAttrs device then
|
||||
throw "Every device in config/network.nix.devices must be an attrset."
|
||||
else if !(device ? type) then
|
||||
throw "Every device in config/network.nix.devices must define a type."
|
||||
else
|
||||
device.type == "auto_ssh"
|
||||
) devices;
|
||||
|
||||
getAutoSshDomains = autoSshDevices:
|
||||
map (device:
|
||||
if !(device ? domain) || !builtins.isString device.domain || device.domain == "" then
|
||||
throw "Every auto_ssh device in config/network.nix must define domain as a non-empty string."
|
||||
else
|
||||
device.domain
|
||||
) (builtins.attrValues autoSshDevices);
|
||||
|
||||
getAutoSshConfig = deviceName: device:
|
||||
if !(device ? auto_ssh) then
|
||||
throw "Auto SSH device '${deviceName}' is missing required field: auto_ssh."
|
||||
else if !builtins.isAttrs device.auto_ssh then
|
||||
throw "Auto SSH device '${deviceName}' field auto_ssh must be an attrset."
|
||||
else
|
||||
device.auto_ssh;
|
||||
|
||||
getRemotePortMap = device:
|
||||
if !(device ? auto_ssh) then
|
||||
[]
|
||||
else if !builtins.isAttrs device.auto_ssh then
|
||||
throw "Device auto_ssh must be an attrset when present."
|
||||
else if !(device.auto_ssh ? remotePortMap) then
|
||||
[]
|
||||
else if !builtins.isList device.auto_ssh.remotePortMap then
|
||||
throw "Device auto_ssh.remotePortMap must be a list of { localPort = int; remotePort = int; }."
|
||||
else if !lib.all (entry:
|
||||
builtins.isAttrs entry
|
||||
&& entry ? localPort
|
||||
&& entry ? remotePort
|
||||
&& builtins.isInt entry.localPort
|
||||
&& builtins.isInt entry.remotePort
|
||||
) device.auto_ssh.remotePortMap then
|
||||
throw "Every remotePortMap entry must be { localPort = int; remotePort = int; }."
|
||||
else
|
||||
device.auto_ssh.remotePortMap;
|
||||
|
||||
isSafeName = name:
|
||||
builtins.match "^[a-z_][a-z0-9_-]*$" name != null;
|
||||
in
|
||||
{
|
||||
inherit getDevices getAutoSshDevices getAutoSshDomains getAutoSshConfig getRemotePortMap isSafeName;
|
||||
}
|
||||
141
validation/endpoints.nix
Normal file
141
validation/endpoints.nix
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
let
|
||||
lib = import <nixpkgs/lib>;
|
||||
|
||||
allowedEndpointKeys = [ "type" "domain" "endpoint" "port" "force_ssl" "content" ];
|
||||
allowedProxyContentKeys = [ "type" "ip" "port" "proxyWebsockets" ];
|
||||
allowedWebContentKeys = [ "type" "files" ];
|
||||
allowedWebFileKeys = [ "path" "filePath" "contentType" "status" ];
|
||||
|
||||
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;
|
||||
|
||||
ensurePath = value:
|
||||
builtins.isString value
|
||||
&& builtins.substring 0 1 value == "/";
|
||||
|
||||
ensurePort = value:
|
||||
builtins.isInt value && value > 0 && value <= 65535;
|
||||
|
||||
validateEndpointShape = index: endpoint:
|
||||
let
|
||||
_ = if !builtins.isAttrs endpoint then throw "Endpoint at index ${toString index} must be an attrset." else null;
|
||||
__ = ensureNoUnknownKeys "Endpoint at index ${toString index}" endpoint allowedEndpointKeys;
|
||||
___ =
|
||||
if endpoint ? forceSsl then
|
||||
throw "Endpoint at index ${toString index} uses forceSsl. Use force_ssl instead."
|
||||
else
|
||||
null;
|
||||
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 [ "proxy" "web" ] then
|
||||
null
|
||||
else
|
||||
throw "Endpoint at index ${toString index} type must be 'proxy' or 'web'.";
|
||||
_domain =
|
||||
if endpoint ? domain && builtins.isString endpoint.domain && endpoint.domain != "" then
|
||||
null
|
||||
else
|
||||
throw "Endpoint at index ${toString index} must define a non-empty domain string.";
|
||||
_endpoint =
|
||||
if endpoint ? endpoint && ensurePath endpoint.endpoint then
|
||||
null
|
||||
else
|
||||
throw "Endpoint at index ${toString index} must define endpoint as a path starting with '/'.";
|
||||
_port =
|
||||
if endpoint ? port && ensurePort endpoint.port then
|
||||
null
|
||||
else
|
||||
throw "Endpoint at index ${toString index} must define port as int in range 1..65535.";
|
||||
_forceSsl =
|
||||
if endpoint ? force_ssl && builtins.isBool endpoint.force_ssl then
|
||||
null
|
||||
else
|
||||
throw "Endpoint at index ${toString index} must define force_ssl as a bool.";
|
||||
contentValue =
|
||||
if endpoint ? content && builtins.isAttrs endpoint.content then
|
||||
endpoint.content
|
||||
else
|
||||
throw "Endpoint at index ${toString index} must define content as an attrset.";
|
||||
_content =
|
||||
if typeValue == "proxy" then
|
||||
let
|
||||
____ = ensureNoUnknownKeys "Proxy content at endpoint index ${toString index}" contentValue allowedProxyContentKeys;
|
||||
in
|
||||
if !(contentValue ? type) || !builtins.isString contentValue.type then
|
||||
throw "Proxy endpoint at index ${toString index} must define content.type as a string."
|
||||
else if !(contentValue ? ip) || !builtins.isString contentValue.ip || contentValue.ip == "" then
|
||||
throw "Proxy endpoint at index ${toString index} must define content.ip as a non-empty string."
|
||||
else if !(contentValue ? port) || !ensurePort contentValue.port then
|
||||
throw "Proxy endpoint at index ${toString index} must define content.port as int in range 1..65535."
|
||||
else if !(contentValue ? proxyWebsockets) || !builtins.isBool contentValue.proxyWebsockets then
|
||||
throw "Proxy endpoint at index ${toString index} must define content.proxyWebsockets as a bool."
|
||||
else
|
||||
null
|
||||
else
|
||||
let
|
||||
____ = ensureNoUnknownKeys "Web content at endpoint index ${toString index}" contentValue allowedWebContentKeys;
|
||||
filesValue =
|
||||
if contentValue ? files && builtins.isList contentValue.files then
|
||||
contentValue.files
|
||||
else
|
||||
throw "Web endpoint at index ${toString index} must define content.files as a list.";
|
||||
_____ =
|
||||
if contentValue ? type && contentValue.type == "store" then
|
||||
null
|
||||
else
|
||||
throw "Web endpoint at index ${toString index} must define content.type = 'store'.";
|
||||
______ = lib.imap0 (fileIndex: file:
|
||||
let
|
||||
_______ =
|
||||
if builtins.isAttrs file then
|
||||
null
|
||||
else
|
||||
throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must be an attrset.";
|
||||
________ = ensureNoUnknownKeys "Web endpoint at index ${toString index} file at position ${toString fileIndex}" file allowedWebFileKeys;
|
||||
_________ =
|
||||
if file ? path && builtins.isString file.path && file.path != "" && builtins.substring 0 1 file.path != "/" then
|
||||
null
|
||||
else
|
||||
throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must define path as a relative path string.";
|
||||
__________ =
|
||||
if file ? filePath && (builtins.isString file.filePath || builtins.isPath file.filePath) then
|
||||
null
|
||||
else
|
||||
throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must define filePath as string or path.";
|
||||
___________ =
|
||||
if file ? contentType && builtins.isString file.contentType && file.contentType != "" then
|
||||
null
|
||||
else
|
||||
throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must define contentType as non-empty string.";
|
||||
____________ =
|
||||
if file ? status && builtins.isInt file.status then
|
||||
null
|
||||
else
|
||||
throw "Web endpoint at index ${toString index} file at position ${toString fileIndex} must define status as int.";
|
||||
in
|
||||
file
|
||||
) filesValue;
|
||||
in
|
||||
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;
|
||||
in
|
||||
{
|
||||
inherit validateEndpointShape validateEndpointsShape;
|
||||
}
|
||||
40
validation/network_devices.nix
Normal file
40
validation/network_devices.nix
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
let
|
||||
lib = import <nixpkgs/lib>;
|
||||
|
||||
getDevices = net:
|
||||
if net ? devices && builtins.isAttrs net.devices then
|
||||
net.devices
|
||||
else
|
||||
throw "config/network.nix must define devices as an attrset.";
|
||||
|
||||
getLocalDevices = devices:
|
||||
lib.filterAttrs (deviceName: device:
|
||||
if !builtins.isAttrs device then
|
||||
throw "Device '${deviceName}' must be an attrset."
|
||||
else if !(device ? type) || !builtins.isString device.type then
|
||||
throw "Device '${deviceName}' must define type as a string."
|
||||
else if !(device ? ip) || !builtins.isString device.ip || device.ip == "" then
|
||||
throw "Device '${deviceName}' must define ip as a non-empty string."
|
||||
else
|
||||
device.type == "local"
|
||||
) devices;
|
||||
|
||||
validateReservationShape = deviceName: reservation:
|
||||
let
|
||||
_ = if !builtins.isAttrs reservation then throw "Device '${deviceName}' reservation must be an attrset." else null;
|
||||
_hw =
|
||||
if reservation ? hw_address && builtins.isString reservation.hw_address && reservation.hw_address != "" then
|
||||
null
|
||||
else
|
||||
throw "Device '${deviceName}' reservation must define hw_address as a non-empty string.";
|
||||
_host =
|
||||
if reservation ? hostname && builtins.isString reservation.hostname && reservation.hostname != "" then
|
||||
null
|
||||
else
|
||||
throw "Device '${deviceName}' reservation must define hostname as a non-empty string.";
|
||||
in
|
||||
reservation;
|
||||
in
|
||||
{
|
||||
inherit getDevices getLocalDevices validateReservationShape;
|
||||
}
|
||||
71
validation/secrets.nix
Normal file
71
validation/secrets.nix
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
let
|
||||
lib = import <nixpkgs/lib>;
|
||||
helperNames = [ "getRuntimePath" "getSopsKey" ];
|
||||
|
||||
validateLeaf = context: leaf:
|
||||
if builtins.isString leaf || builtins.isPath leaf then
|
||||
# String or path: file path (binary or single-value secret)
|
||||
leaf
|
||||
else if builtins.isAttrs leaf then
|
||||
# Object: must have 'file', may have 'keys' list and optional deployment metadata
|
||||
let
|
||||
_ =
|
||||
if leaf ? file && (builtins.isPath leaf.file || builtins.isString leaf.file) then
|
||||
null
|
||||
else
|
||||
throw "${context}.file must be a path or string.";
|
||||
__ =
|
||||
if !(leaf ? keys) || (builtins.isList leaf.keys && builtins.all builtins.isString leaf.keys) then
|
||||
null
|
||||
else
|
||||
throw "${context}.keys must be a list of strings when provided.";
|
||||
___ =
|
||||
if !(leaf ? path) || builtins.isString leaf.path then
|
||||
null
|
||||
else
|
||||
throw "${context}.path must be a string when provided.";
|
||||
____ =
|
||||
if !(leaf ? owner) || builtins.isString leaf.owner then
|
||||
null
|
||||
else
|
||||
throw "${context}.owner must be a string when provided.";
|
||||
_____ =
|
||||
if !(leaf ? group) || builtins.isString leaf.group then
|
||||
null
|
||||
else
|
||||
throw "${context}.group must be a string when provided.";
|
||||
______ =
|
||||
if !(leaf ? mode) || builtins.isString leaf.mode then
|
||||
null
|
||||
else
|
||||
throw "${context}.mode must be a string when provided.";
|
||||
in
|
||||
leaf
|
||||
else
|
||||
throw "${context} must be a string, path, or an attrset with 'file' key.";
|
||||
|
||||
validateTree = context: node:
|
||||
if builtins.isAttrs node then
|
||||
lib.mapAttrs (name: value:
|
||||
if lib.elem name helperNames then
|
||||
value
|
||||
else
|
||||
validateTree "${context}.${name}" value
|
||||
) node
|
||||
else
|
||||
throw "${context} must be an attrset at non-leaf level.";
|
||||
|
||||
getSecretsConfig = secretsConfig:
|
||||
let
|
||||
_ =
|
||||
if builtins.isAttrs secretsConfig then
|
||||
null
|
||||
else
|
||||
throw "config/secrets.nix must evaluate to an attrset.";
|
||||
__ = validateTree "config/secrets.nix" secretsConfig;
|
||||
in
|
||||
secretsConfig;
|
||||
in
|
||||
rec {
|
||||
inherit getSecretsConfig validateLeaf validateTree;
|
||||
}
|
||||
34
validation/service/common.nix
Normal file
34
validation/service/common.nix
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
let
|
||||
ensureAttrset = context: value:
|
||||
if builtins.isAttrs value then
|
||||
value
|
||||
else
|
||||
throw "${context} must be an attrset.";
|
||||
|
||||
ensureString = context: value:
|
||||
if builtins.isString value then
|
||||
value
|
||||
else
|
||||
throw "${context} must be a string.";
|
||||
|
||||
ensureNonEmptyString = context: value:
|
||||
if builtins.isString value && value != "" then
|
||||
value
|
||||
else
|
||||
throw "${context} must be a non-empty string.";
|
||||
|
||||
ensureInt = context: value:
|
||||
if builtins.isInt value then
|
||||
value
|
||||
else
|
||||
throw "${context} must be an int.";
|
||||
|
||||
ensureList = context: value:
|
||||
if builtins.isList value then
|
||||
value
|
||||
else
|
||||
throw "${context} must be a list.";
|
||||
in
|
||||
rec {
|
||||
inherit ensureAttrset ensureString ensureNonEmptyString ensureInt ensureList;
|
||||
}
|
||||
20
validation/service/continuwuity.nix
Normal file
20
validation/service/continuwuity.nix
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
let
|
||||
common = import ./common.nix;
|
||||
in
|
||||
rec {
|
||||
getTrustedServers = serviceData:
|
||||
let
|
||||
matrix =
|
||||
if serviceData ? matrix then
|
||||
common.ensureAttrset "config/services.nix matrix" serviceData.matrix
|
||||
else
|
||||
throw "config/services.nix must define matrix attrset.";
|
||||
|
||||
trustedServers =
|
||||
if matrix ? trusted_servers then
|
||||
common.ensureList "config/services.nix matrix.trusted_servers" matrix.trusted_servers
|
||||
else
|
||||
throw "config/services.nix matrix.trusted_servers must exist.";
|
||||
in
|
||||
trustedServers;
|
||||
}
|
||||
36
validation/service/kiwix.nix
Normal file
36
validation/service/kiwix.nix
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
let
|
||||
common = import ./common.nix;
|
||||
in
|
||||
rec {
|
||||
getKiwix = serviceData:
|
||||
let
|
||||
kiwix =
|
||||
if serviceData ? kiwix then
|
||||
common.ensureAttrset "config/services.nix kiwix" serviceData.kiwix
|
||||
else
|
||||
throw "config/services.nix must define kiwix attrset.";
|
||||
|
||||
rootDir =
|
||||
if kiwix ? root_dir then
|
||||
common.ensureNonEmptyString "config/services.nix kiwix.root_dir" kiwix.root_dir
|
||||
else
|
||||
throw "config/services.nix kiwix.root_dir must exist.";
|
||||
|
||||
webPort =
|
||||
if kiwix ? port then
|
||||
common.ensureInt "config/services.nix kiwix.port" kiwix.port
|
||||
else
|
||||
throw "config/services.nix kiwix.port must exist.";
|
||||
|
||||
zimUrls =
|
||||
if kiwix ? urls then
|
||||
common.ensureList "config/services.nix kiwix.urls" kiwix.urls
|
||||
else
|
||||
throw "config/services.nix kiwix.urls must exist.";
|
||||
in
|
||||
{
|
||||
root_dir = rootDir;
|
||||
port = webPort;
|
||||
urls = zimUrls;
|
||||
};
|
||||
}
|
||||
51
validation/service/qbittorrent.nix
Normal file
51
validation/service/qbittorrent.nix
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
let
|
||||
common = import ./common.nix;
|
||||
in
|
||||
rec {
|
||||
getQbittorrent = serviceData:
|
||||
let
|
||||
qbt =
|
||||
if serviceData ? qbittorrent then
|
||||
common.ensureAttrset "config/services.nix qbittorrent" serviceData.qbittorrent
|
||||
else
|
||||
throw "config/services.nix must define qbittorrent attrset.";
|
||||
|
||||
webPort =
|
||||
if qbt ? port then
|
||||
common.ensureInt "config/services.nix qbittorrent.port" qbt.port
|
||||
else
|
||||
throw "config/services.nix qbittorrent.port must exist.";
|
||||
|
||||
rootDir =
|
||||
if qbt ? root_dir then
|
||||
common.ensureNonEmptyString "config/services.nix qbittorrent.root_dir" qbt.root_dir
|
||||
else
|
||||
throw "config/services.nix qbittorrent.root_dir must exist.";
|
||||
|
||||
vpn =
|
||||
if qbt ? vpn then
|
||||
common.ensureAttrset "config/services.nix qbittorrent.vpn" qbt.vpn
|
||||
else
|
||||
throw "config/services.nix qbittorrent.vpn must exist.";
|
||||
|
||||
usernameFile =
|
||||
if vpn ? username_file then
|
||||
common.ensureNonEmptyString "config/services.nix qbittorrent.vpn.username_file" vpn.username_file
|
||||
else
|
||||
throw "config/services.nix qbittorrent.vpn.username_file must exist.";
|
||||
|
||||
passwordFile =
|
||||
if vpn ? password_file then
|
||||
common.ensureNonEmptyString "config/services.nix qbittorrent.vpn.password_file" vpn.password_file
|
||||
else
|
||||
throw "config/services.nix qbittorrent.vpn.password_file must exist.";
|
||||
in
|
||||
{
|
||||
port = webPort;
|
||||
root_dir = rootDir;
|
||||
vpn = {
|
||||
username_file = usernameFile;
|
||||
password_file = passwordFile;
|
||||
};
|
||||
};
|
||||
}
|
||||
57
validation/storage.nix
Normal file
57
validation/storage.nix
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
let
|
||||
lib = import <nixpkgs/lib>;
|
||||
|
||||
validateStorageEntry = name: entry:
|
||||
let
|
||||
_ =
|
||||
if builtins.isAttrs entry then
|
||||
null
|
||||
else
|
||||
throw "config/storage.nix entry '${name}' must be an attrset.";
|
||||
__ =
|
||||
if entry ? path && builtins.isString entry.path && entry.path != "" then
|
||||
null
|
||||
else
|
||||
throw "config/storage.nix entry '${name}' must define path as a non-empty string.";
|
||||
___ =
|
||||
if entry ? source && builtins.isString entry.source && entry.source != "" then
|
||||
null
|
||||
else
|
||||
throw "config/storage.nix entry '${name}' must define source as a non-empty string.";
|
||||
____ =
|
||||
if entry ? type && builtins.isString entry.type && entry.type != "" then
|
||||
null
|
||||
else
|
||||
throw "config/storage.nix entry '${name}' must define type as a non-empty string.";
|
||||
_____ =
|
||||
if entry ? options && builtins.isList entry.options then
|
||||
null
|
||||
else
|
||||
throw "config/storage.nix entry '${name}' must define options as a list.";
|
||||
______ =
|
||||
if lib.all builtins.isString entry.options then
|
||||
null
|
||||
else
|
||||
throw "config/storage.nix entry '${name}' options must be a list of strings.";
|
||||
_______ =
|
||||
if !(entry ? extra) || builtins.isAttrs entry.extra then
|
||||
null
|
||||
else
|
||||
throw "config/storage.nix entry '${name}' field extra must be an attrset when present.";
|
||||
in
|
||||
entry;
|
||||
|
||||
getStorageConfig = config:
|
||||
let
|
||||
_ =
|
||||
if builtins.isAttrs config then
|
||||
null
|
||||
else
|
||||
throw "config/storage.nix must evaluate to an attrset.";
|
||||
__ = lib.mapAttrs validateStorageEntry config;
|
||||
in
|
||||
config;
|
||||
in
|
||||
rec {
|
||||
inherit getStorageConfig validateStorageEntry;
|
||||
}
|
||||
40
validation/web.nix
Normal file
40
validation/web.nix
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
let
|
||||
lib = import <nixpkgs/lib>;
|
||||
|
||||
ensureStoreName = name:
|
||||
if builtins.match "^[A-Za-z_][A-Za-z0-9_-]*$" name != null then
|
||||
name
|
||||
else
|
||||
throw "config/web.nix store name '${name}' is invalid. Use letters, numbers, '_' or '-', starting with a letter or '_' .";
|
||||
|
||||
validateStore = name: store:
|
||||
let
|
||||
_ = ensureStoreName name;
|
||||
__ =
|
||||
if builtins.isAttrs store then
|
||||
null
|
||||
else
|
||||
throw "config/web.nix stores.${name} must be an attrset.";
|
||||
___ =
|
||||
if store ? root && builtins.isPath store.root then
|
||||
null
|
||||
else
|
||||
throw "config/web.nix stores.${name}.root must be a Nix path.";
|
||||
in
|
||||
store;
|
||||
|
||||
getStores = webConfig:
|
||||
let
|
||||
stores =
|
||||
if builtins.isAttrs webConfig && webConfig ? stores && builtins.isAttrs webConfig.stores then
|
||||
webConfig.stores
|
||||
else
|
||||
throw "config/web.nix must define stores as an attrset.";
|
||||
|
||||
_ = lib.mapAttrs validateStore stores;
|
||||
in
|
||||
stores;
|
||||
in
|
||||
rec {
|
||||
inherit getStores;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue