summary refs log blame commit diff
path: root/lib/types.nix
blob: 156d72ac5e73eaea52b22e0f423063997720e2fc (plain) (tree)
1
2
3
4
5
6
7
8
9







                                                                    
                          


     
                                            
                                      
                            
 



                                       
 
                           
                                                                                                               



                                                                     
                                                                                           
                                                                              
                                      









                                           
                               


                            
                                                                              

      
 
               
 












                                                
                                                  


                                







                                                                          





                                       

                             
                                        








                                             


                                               
                                                                                                                      

      



                                                                                            






                                                                                


                                                                          
                                


                                      
                                                  
                                            
                                                  
                                                      


                                                                                        
                                                      

      



































                                                                                     


                                                                 
                                

                   
                                                                                                 

      





                                                                 






                                                                           



                                                                
                                                     




                                                                                 

                                           

      














                                                                     

                              

                                                                           
                                                    
                        
                               




      
# 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 {

  isType = type: x: (x._type or "") == type;
  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 = isType "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;
    };

  };

}