summary refs log tree commit diff
path: root/lib/types.nix
blob: 8deb379c25a90f3df9dca37f4b54591105e8a7bb (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
# 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;
  };


  isOptionType = isType "option-type";
  mkOptionType =
    { # Human-readable representation of the type.
      name
    , # Function applied to each definition that should return true if
      # its type-correct, false otherwise.
      check ? (x: true)
    , # Merge a list of definitions together into a single value.
      merge ? mergeDefaultOption
    , # Return a flat list of sub-options.  Used to generate
      # documentation.
      getSubOptions ? prefix: {}
    }:
    { _type = "option-type";
      inherit name check merge getSubOptions;
    };


  types = rec {

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

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

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

    str = mkOptionType {
      name = "string";
      check = builtins.isString;
      merge = mergeOneOption;
    };

    # Deprecated; should not be used because it quietly concatenates
    # strings, which is usually not what you want.
    string = mkOptionType {
      name = "string";
      check = builtins.isString;
      merge = args: lib.concatStrings;
    };

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

    commas = mkOptionType {
      name = "string";
      check = builtins.isString;
      merge = args: lib.concatStringsSep ",";
    };

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

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

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

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

    # 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 = args: defs: imap (n: def: elemType.merge (addToPrefix args (toString n)) [def]) (concatLists defs);
      getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["*"]);
    };

    attrsOf = elemType: mkOptionType {
      name = "attribute set of ${elemType.name}s";
      check = x: isAttrs x && all elemType.check (lib.attrValues x);
      merge = args: lib.zipAttrsWith (name:
        elemType.merge (addToPrefix (args // { inherit name; }) name));
      getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<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 = args: defs: attrOnly.merge args (imap convertIfList defs);
        getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name?>"]);
      };

    uniq = elemType: mkOptionType {
      inherit (elemType) name check;
      merge = mergeOneOption;
      getSubOptions = elemType.getSubOptions;
    };

    none = elemType: mkOptionType {
      inherit (elemType) name check;
      merge = args: list:
        throw "No definitions are allowed for the option `${showOption args.prefix}'.";
      getSubOptions = elemType.getSubOptions;
    };

    nullOr = elemType: mkOptionType {
      name = "null or ${elemType.name}";
      check = x: builtins.isNull x || elemType.check x;
      merge = args: defs:
        if all isNull defs then null
        else if any isNull defs then
          throw "The option `${showOption args.prefix}' is defined both null and not null, in ${showFiles args.files}."
        else elemType.merge args defs;
      getSubOptions = elemType.getSubOptions;
    };

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

    submodule = opts:
      let opts' = toList opts; in
      mkOptionType rec {
        name = "submodule";
        check = x: isAttrs x || builtins.isFunction x;
        merge = args: defs:
          let
            coerce = def: if builtins.isFunction def then def else { config = def; };
            modules = opts' ++ map coerce defs;
          in (evalModules { inherit modules args; prefix = args.prefix; }).config;
        getSubOptions = prefix: (evalModules
          { modules = opts'; inherit prefix;
            # FIXME: hack to get shit to evaluate.
            args = { name = ""; }; }).options;
      };

    # 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";
    };

  };


  /* Helper function. */
  addToPrefix = args: name: args // { prefix = args.prefix ++ [name]; };

}