summary refs log tree commit diff
path: root/lib/types.nix
blob: 7cfd8e606d7339c143eb33ab8177a5a4b42bea07 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# Definitions related to run-time type checking.  Used in particular
# to type-check NixOS configurations.

let lib = import ./default.nix; in

with lib.lists;
with lib.attrsets;
with lib.options;
with lib.trivial;
with lib.modules;

rec {

  isType = type: x: (x._type or "") == type;
  typeOf = x: x._type or "";

  setType = typeName: value: value // {
    _type = typeName;
  };


  # name (name of the type)
  # check (check the config value)
  # merge (default merge function)
  # docPath (path concatenated to the option name contained in the option set)
  isOptionType = isType "option-type";
  mkOptionType =
    { name
    , check ? (x: true)
    , merge ? mergeDefaultOption
    , merge' ? args: merge
    , docPath ? lib.id
    }:

    { _type = "option-type";
      inherit name check merge merge' docPath;
    };


  types = rec {

    unspecified = mkOptionType {
      name = "unspecified";
    };

    bool = mkOptionType {
      name = "boolean";
      check = builtins.isBool;
      merge = fold lib.or false;
    };

    int = mkOptionType {
      name = "integer";
      check = builtins.isInt;
    };

    string = mkOptionType {
      name = "string";
      check = builtins.isString;
      merge = lib.concatStrings;
    };

    # Like ‘string’, but add newlines between every value.  Useful for
    # configuration file contents.
    lines = mkOptionType {
      name = "string";
      check = builtins.isString;
      merge = lib.concatStringsSep "\n";
    };

    envVar = mkOptionType {
      name = "environment variable";
      inherit (string) check;
      merge = lib.concatStringsSep ":";
    };

    attrs = mkOptionType {
      name = "attribute set";
      check = isAttrs;
      merge = fold lib.mergeAttrs {};
    };

    # derivation is a reserved keyword.
    package = mkOptionType {
      name = "derivation";
      check = isDerivation;
    };

    path = mkOptionType {
      name = "path";
      # Hacky: there is no ‘isPath’ primop.
      check = x: builtins.unsafeDiscardStringContext (builtins.substring 0 1 (toString x)) == "/";
    };

    # drop this in the future:
    list = builtins.trace "types.list is deprecated; use types.listOf instead" types.listOf;

    listOf = elemType: mkOptionType {
      name = "list of ${elemType.name}s";
      check = value: isList value && all elemType.check value;
      merge = defs: map (def: elemType.merge [def]) (concatLists defs);
      docPath = path: elemType.docPath (path + ".*");
    };

    attrsOf = elemType: mkOptionType {
      name = "attribute set of ${elemType.name}s";
      check = x: isAttrs x && all elemType.check (lib.attrValues x);
      merge = lib.zipAttrsWith (name: elemType.merge' { inherit name; });
      docPath = path: elemType.docPath (path + ".<name>");
    };

    # List or attribute set of ...
    loaOf = elemType:
      let
        convertIfList = defIdx: def:
          if isList def then
            listToAttrs (
              flip imap def (elemIdx: elem:
                { name = "unnamed-${toString defIdx}.${toString elemIdx}"; value = elem; }))
          else
            def;
        listOnly = listOf elemType;
        attrOnly = attrsOf elemType;

      in mkOptionType {
        name = "list or attribute set of ${elemType.name}s";
        check = x:
          if isList x       then listOnly.check x
          else if isAttrs x then attrOnly.check x
          else false;
        merge = defs: attrOnly.merge (imap convertIfList defs);
        docPath = path: elemType.docPath (path + ".<name?>");
      };

    uniq = elemType: mkOptionType {
      inherit (elemType) name check docPath;
      merge = list:
        if length list == 1 then
          head list
        else
          throw "Multiple definitions of ${elemType.name}. Only one is allowed for this option.";
    };

    none = elemType: mkOptionType {
      inherit (elemType) name check docPath;
      merge = list:
        throw "No definitions are allowed for this option.";
    };

    nullOr = elemType: mkOptionType {
      inherit (elemType) docPath;
      name = "null or ${elemType.name}";
      check = x: builtins.isNull x || elemType.check x;
      merge = defs:
        if all isNull defs then null
        else if any isNull defs then
          throw "Some but not all values are null."
        else elemType.merge defs;
    };

    functionTo = elemType: mkOptionType {
      name = "function that evaluates to a(n) ${elemType.name}";
      check = builtins.isFunction;
      merge = fns:
        args: elemType.merge (map (fn: fn args) fns);
    };

    submodule = opts: mkOptionType rec {
      name = "submodule";
      check = x: isAttrs x || builtins.isFunction x;
      # FIXME: make error messages include the parent attrpath.
      merge = merge' {};
      merge' = args: defs:
        let
          coerce = def: if builtins.isFunction def then def else { config = def; };
          modules = (toList opts) ++ map coerce defs;
        in (evalModules modules args).config;
    };

    # Obsolete alternative to configOf.  It takes its option
    # declarations from the ‘options’ attribute of containing option
    # declaration.
    optionSet = mkOptionType {
      name = /* builtins.trace "types.optionSet is deprecated; use types.submodule instead" */ "option set";
    };

  };

}