summary refs log tree commit diff
path: root/pkgs/lib/types.nix
blob: 9dce885b1826314909f7adceffdc004b5f9851c9 (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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# Definitions related to run-time type checking.  Used in particular
# to type-check NixOS configurations.

let lib = import ./default.nix; in

with import ./lists.nix;
with import ./attrsets.nix;
with import ./options.nix;
with import ./trivial.nix;

rec {

  hasType = x: isAttrs x && x ? _type;
  typeOf = x: x._type or "";

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


  # name (name of the type)
  # check (check the config value. Before returning false it should trace the bad value eg using traceValIfNot)
  # merge (default merge function)
  # iter (iterate on all elements contained in this type)
  # fold (fold all elements contained in this type)
  # hasOptions (boolean: whatever this option contains an option set)
  # delayOnGlobalEval (boolean: should properties go through the evaluation of this option)
  # docPath (path concatenated to the option name contained in the option set)
  isOptionType = attrs: typeOf attrs == "option-type";
  mkOptionType =
    { name
    , check ? (x: true)
    , merge ? mergeDefaultOption
    # Handle complex structure types.
    , iter ? (f: path: v: f path v)
    , fold ? (op: nul: v: op v nul)
    , docPath ? lib.id
    # If the type can contains option sets.
    , hasOptions ? false
    , delayOnGlobalEval ? false
    }:

    { _type = "option-type";
      inherit name check merge iter fold docPath hasOptions delayOnGlobalEval;
    };


  types = rec {

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

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

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

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

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

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

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

    path = mkOptionType {
      name = "path";
      # Hacky: there is no ‘isPath’ primop.
      check = lib.traceValIfNot (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: lib.traceValIfNot isList value && all elemType.check value;
      merge = concatLists;
      iter = f: path: list: map (elemType.iter f (path + ".*")) list;
      fold = op: nul: list: lib.fold (e: l: elemType.fold op l e) nul list;
      docPath = path: elemType.docPath (path + ".*");
      inherit (elemType) hasOptions;

      # You cannot define multiple configurations of one entity, therefore
      # no reason justify to delay properties inside list elements.
      delayOnGlobalEval = false;
    };

    attrsOf = elemType: mkOptionType {
      name = "attribute set of ${elemType.name}s";
      check = x: lib.traceValIfNot isAttrs x
        && all elemType.check (lib.attrValues x); 
      merge = lib.zipAttrsWith (name: elemType.merge);
      iter = f: path: set: lib.mapAttrs (name: elemType.iter f (path + "." + name)) set;
      fold = op: nul: set: fold (e: l: elemType.fold op l e) nul (lib.attrValues set);
      docPath = path: elemType.docPath (path + ".<name>");
      inherit (elemType) hasOptions delayOnGlobalEval;
    };

    # List or attribute set of ...
    loaOf = elemType:
      let
        convertIfList = defIdx: def:
          if isList def then
            listToAttrs (
              flip imap def (elemIdx: elem:
                nameValuePair "unnamed-${toString defIdx}.${toString elemIdx}" 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 lib.traceValIfNot (x: false) x;
        ## The merge function returns an attribute set
        merge = defs:
          attrOnly.merge (imap convertIfList defs);
        iter = f: path: def:
          if isList def       then listOnly.iter f path def
          else if isAttrs def then attrOnly.iter f path def
          else throw "Unexpected value";
        fold = op: nul: def:
          if isList def       then listOnly.fold op nul def
          else if isAttrs def then attrOnly.fold op nul def
          else throw "Unexpected value";

        docPath = path: elemType.docPath (path + ".<name?>");
        inherit (elemType) hasOptions delayOnGlobalEval;
      }
    ;

    uniq = elemType: mkOptionType {
      inherit (elemType) name check iter fold docPath hasOptions;
      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 iter fold docPath hasOptions;
      merge = list:
        throw "No definitions are allowed for this option.";
    };

    nullOr = elemType: mkOptionType {
      inherit (elemType) name merge docPath hasOptions;
      check = x: builtins.isNull x || elemType.check x;
      iter = f: path: v: if v == null then v else elemType.iter f path v;
      fold = op: nul: v: if v == null then nul else elemType.fold op nul v;
    };

    functionTo = elemType: mkOptionType {
      name = "function that evaluates to a(n) ${elemType.name}";
      check = lib.traceValIfNot builtins.isFunction;
      merge = fns:
        args: elemType.merge (map (fn: fn args) fns);
      # These are guesses, I don't fully understand iter, fold, delayOnGlobalEval
      iter = f: path: v:
        args: elemType.iter f path (v args);
      fold = op: nul: v:
        args: elemType.fold op nul (v args);
      inherit (elemType) delayOnGlobalEval;
      hasOptions = false;
    };

    # usually used with listOf, attrsOf, loaOf like this:
    # users = mkOption {
    #   type = loaOf optionSet;
    #
    #   # you can omit the list if there is one element only
    #   options = [ {
    #     name = mkOption {
    #       description = "name of the user"
    #       ...
    #     };
    #     # more options here
    #   } { more options } ];
    # }
    # TODO: !!! document passing options as an argument to optionSet,
    # deprecate the current approach.
    optionSet = mkOptionType {
      name = "option set";
      # merge is done in "options.nix > addOptionMakeUp > handleOptionSets"
      merge = lib.id;
      check = x: isAttrs x || builtins.isFunction x;
      hasOptions = true;
      delayOnGlobalEval = true;
    };

  };

}