Flexible data types

These abstract data types can match values with a variety of concrete data types. Some of them are similar to a concrete type but offer alternate ways to restrict them (for example, Enum), and some of them let you combine types and match a union of what they would individually match (for example, Variant and Optional).

The Optional data type

The Optional data type wraps one other data type, and results in a data type that matches anything that type would match plusundef. This is useful for matching values that are allowed to be absent. It takes one required parameter.

The full signature for Optional is:

Optional[<DATA TYPE>]
PositionParameterData typeDefault valueDescription
1Data typeType or Stringnone (you must specify a value)The data type to add undef to.

If you specify a string "my string" as the parameter, it's equivalent to using Optional[Enum["my string"]] — it matches only that exact string value or undef.

Optional[<DATA TYPE>] is equivalent to Variant[ <DATA TYPE>, Undef ].

Examples:

Optional[String]
Matches any string or undef.
Optional[Array[Integer[0, 10]]]
Matches an array of integers between 0 and 10, or undef.
Optional["present"]
Matches the exact string "present" or undef.

The NotUndef data type

The NotUndef type matches any value except undef. It can also wrap one other data type, resulting in a type that matches anything the original type would match except undef. It accepts one optional parameter.

The full signature for NotUndef is:

NotUndef[<DATA TYPE>]
PositionParameterData typeDefault valueDescription
1Data typeType or StringAnyThe data type to subtract undef from.

If you specify a string as a parameter for NotUndef, it's equivalent to writing NotUndef[Enum["my string"]] — it matches only that exact string value. This doesn’t actually subtract anything, because the Enum wouldn’t have matched undef anyway, but it's a convenient notation for mandatory keys in Struct schema hashes.

The Variant data type

The Variant data type combines any number of other data types, and results in a type that matches the union of what any of those data types would match. It takes any number of parameters, and requires at least one.

The full signature for Variant is:

Variant[ <DATA TYPE>, (<DATA TYPE, ...) ]
PositionParameterData typeDefault valueDescription
1 and upData typeTypenone (required)A data type to add to the resulting compound data type. You must provide at least one data type parameter, and can provide any number of additional ones.

Examples:

Variant[Integer, Float]
Matches any integer or floating point number (equivalent to Numeric).
Variant[Enum['true', 'false'], Boolean]
matches 'true', 'false', true, or false.

The Pattern data type

The Pattern data type only matches strings, but it provides an alternate way to restrict which strings it matches. It takes any number of regular expressions, and results in a data type that matches any strings that would match any of those regular expressions. It takes any number of parameters, and requires at least one.

The full signature for Pattern is:

Pattern[ <REGULAR EXPRESSION>, (<REGULAR EXPRESSION>, ...) ]
PositionParameterData typeDefault valueDescription
1 and upRegular expressionRegexpnone (required)A regular expression describing a set of strings that the resulting data type matches. You must provide at least one regular expression parameter, and can provide any number of additional ones.

You can use capture groups in the regular expressions, but they won’t cause any variables, like $1, to be set.

Examples:

Pattern[/\A[a-z].*/]
Matches any string that begins with a lowercase letter.
Pattern[/\A[a-z].*/, /\ANone\Z/]
Matches the above or the exact string None.

The Enum data type

The Enum data type only matches strings, but it provides an alternate way to restrict which strings it matches. It takes any number of strings, and results in a data type that matches any string values that exactly match one of those strings. Unlike the == operator, this matching is case-sensitive. It takes any number of parameters, and requires at least one.

The full signature for Enum is:

Enum[ <OPTION>, (<OPTION>, ...) ]
PositionParameterData typeDefault valueDescription
1 and upOptionStringnone (required)One of the literal string values that the resulting data type matches. You must provide at least one option parameter, and can provide any number of additional ones.

Examples:

Enum['stopped', 'running']
Matches the strings 'stopped' and 'running', and no other values.
Enum['true', 'false']
Matches the strings 'true' and 'false', and no other values. Does not match the boolean values true or false (without quotes).

The Tuple data type

The Tuple type only matches arrays, but it lets you specify different data types for every element of the array, in order. It takes any number of parameters, and requires at least one.

The full signature for Tuple is:

Tuple[ <CONTENT TYPE>, (<CONTENT TYPE>, ..., <MIN SIZE>, <MAX SIZE>) ]
PositionParameterData typeDefault valueDescription
1 and upContent typeTypenone (required)What kind of values the array contains at the given position. You must provide at least one content type parameter, and can provide any number of additional ones.
-2 (second-last)Minimum sizeIntegernumber of content typesThe minimum number of elements in the array. If this is smaller than the number of content types you provided, any elements beyond the minimum are optional; however, if present, they must still match the provided content types. This parameter accepts the value default, but this won’t use the default value; instead, it means 0 (all elements optional).
-1 (last)Maximum sizeIntegernumber of content types

The maximum number of elements in the array. You cannot specify a maximum without also specifying a minimum. If the maximum is larger than the number of content types you provided, it means the array can contain any number of additional elements, which all must match the last content type. This parameter accepts the value default, but this won’t use the default value; instead, it means infinity (any number of elements matching the final content type).

Don't set the maximum smaller than the number of content types you provide.

Examples:

Tuple[String, Integer]
Matches a two-element array containing a string followed by an integer, like ["hi", 2].
Tuple[String, Integer, 1]
Matches the above or a one-element array containing only a string.
Tuple[String, Integer, 1, 4]
Matches an array containing one string followed by zero to three integers.
Tuple[String, Integer, 1, default]
Matches an array containing one string followed by any number of integers.

The Struct data type

The Struct type only matches hashes, but it lets you specify:

  • The name of every allowed key.

  • Whether each key is required or optional.

  • The allowed data type for each of those keys’ values.

It takes one mandatory parameter.

The full signature for Struct is:

Struct[<SCHEMA HASH>]
PositionParameterData typeDefault valueDescription
1Schema hashHash[Variant[String, Optional, NotUndef], Type]none (required)A hash that has all of the allowed keys and data types for the struct.

A Struct’s schema hash must have the same keys as the hashes it matches. Each value must be a data type that matches the allowed values for that key.

The keys in a schema hash are usually strings. They can also be an Optional or NotUndef type with the key’s name as their parameter.

If a key is a string, Puppet uses the value’s type to determine whether it’s optional — because accessing a missing key resolves to the value undef, the key is optional if the value type accepts undef (like Optional[Array]).

Note that this doesn’t distinguish between an explicit value of undef and an absent key. If you want to be more explicit, you can use Optional['my_key'] to indicate that a key can be absent, and NotUndef['my_key'] to make it mandatory. If you use one of these, a value type that accepts undef is only used to decide about explicit undef values, not missing keys.

The following example Struct matches hashes like {mode => 'read', path => '/etc/fstab'}. Both the mode and path keys are mandatory; mode’s value must be one of 'read', 'write', or 'update', and path must be a string of at least one character:

Struct[{mode => Enum[read, write, update],
        path => String[1]}]

The following data type would match the same values as the previous example, but the path key is optional. If present, path must match String[1] or Undef:

Struct[{mode => Enum[read, write, update],
        path => Optional[String[1]]}]

In the following data type, the owner key can be absent, but if it’s present, it must be a string; a value of undef isn’t allowed:

Struct[{mode            => Enum[read, write, update],
        path            => Optional[String[1]],
        Optional[owner] => String[1]}]

In the following data type, the owner key is mandatory, but it allows an explicit undef value:

Struct[{mode            => Enum[read, write, update],
        path            => Optional[String[1]],
        NotUndef[owner] => Optional[String[1]]}]

The SemVer data type

A SemVer instance defines a single semantic version or range of versions. For example, "1.2.3" or ">= 1.0.0 < 2.0.0".

It consists of the following five segments:

  • Major version (required)
  • Minor version (required)
  • Patch version (required)
  • Prerelease tag (optional)
  • Build tag (optional)

You can create an instance of SemVer from a String, individual values, or a hash of individual values.

The signatures are:

type PositiveInteger = Integer[0,default]
type SemVerQualifier = Pattern[/\A(?<part>[0-9A-Za-z-]+)(?:\.\g<part>)*\Z/]
type SemVerString = String[1]
type SemVerHash = Struct[{
  major                =>PositiveInteger,
  minor                =>PositiveInteger,
  patch                =>PositiveInteger,
  Optional[prerelease] =>SemVerQualifier,
  Optional[build]      =>SemVerQualifier
}]

function SemVer.new(SemVerString $str)
function SemVer.new(
        PositiveInteger           $major
        PositiveInteger           $minor
        PositiveInteger           $patch
        Optional[SemVerQualifier] $prerelease = undef
        Optional[SemVerQualifier] $build = undef
        )
function SemVer.new(SemVerHash $hash_args)

Examples:

SemVer.new("1.2.3")
Creates a SemVer instance from a string
SemVer.new(1, 2, 3, "rc4", "5"
Creates a SemVer instance from a list of arguments
SemVer.new(major => 1, minor => 2, patch => 3, prerelease => "rc4", build =>"5")
Creates a SemVer instance from a hash

You can parameterize the SemVer type to restrict which values the type matches. The values are defined by one or more Strings or SemVerRanges.

The full signatures are:

SemVer[<String>] 

The <String> specifies a semantic version string — representing a single version or range of versions. A SemVer instance matches the parameterized type, if the instance is within the range defined by the type. For example:

$t = SemVer['> 1.0.0 < 2.0.0']
notice(SemVer('1.2.3') =~ $t) # true
notice(SemVer('2.3.4') =~ $t) # false

SemVer[ <SemVerRange>,  ( <SemVerRange>, ... ) ]

The SemVer type is accompanied by the SemVerRange type, which you can define to restrict matches to a contiguous version range. For example:

 $t = SemVer[SemVerRange('>=1.0.0 <2.0.0')]
notice(SemVer('1.2.3') =~ $t) # true
notice(SemVer('2.3.4') =~ $t) # false

When you define a parameterized SemVer type using multiple ranges, and the instance is enclosed in at least one of the ranges, the SemVer instance matches the type.

$t = SemVer[SemVerRange('>=1.0.0 <2.0.0'), SemVerRange('>=3.0.0 <4.0.0')]
notice(SemVer('1.2.3') =~ $t) # true
notice(SemVer('2.5.0') =~ $t) # false
notice(SemVer('3.0.0') =~ $t) # true 

If any of the ranges are adjacent or overlap, they get normalized (merged). For example, the following are equal:

SemVer['>=1.0.0 <4.0.0']
SemVer[SemVerRange('>=1.0.0 <3.0.0'), SemVerRange('>=2.0.0 <4.0.0')] # overlap
SemVer[SemVerRange('>=1.0.0 <2.0.0'), SemVerRange('>=2.0.0 <4.0.0')] # adjacent

The SemVerRange data type

An instance of SemVerRange represents a contiguous semantic version range. The string format of a SemVerRange is specified by the SemVer Range Grammar. The SemVerRange type does not support the logical or operator (||).

You can create an instance of SemVerRange (SemVerRange.new) from a String, individual values, or a hash of individual values. The signatures are:

type SemVerRangeString = String[1]
type SemVerRangeHash = Struct[{
  min                   => Variant[default, SemVer],
  Optional[max]         => Variant[default, SemVer],
  Optional[exclude_max] => Boolean
}]

function SemVerRange.new(SemVerRangeString $semver_range_string)

function SemVerRange.new(
           Variant[default,SemVer] $min
           Variant[default,SemVer] $max
           Optional[Boolean]       $exclude_max = undef
         }

function SemVerRange.new(SemVerRangeHash $semver_range_hash)

Examples:

SemVerRange.new(">1.0.0"))
Creates a SemVerRange from a String
SemVerRange.new(SemVer.new("1.0.0"), SemVer.new("2.0.0"), true)
Creates a SemVerRange instance from a list of arguments.
SemVerRange.new(min => SemVer.new("1.0.0"), max => SemVer.new("2.0.0"), exclude_max => true)
Creates a SemVerRange instance from a hash.

By default, the range includes the maximum value so that the following examples are equal:

SemVerRange.new(">= 1.0.0 <= 2.0.0")
SemVerRange.new(SemVer.new("1.0.0"), SemVer.new("2.0.0")) 

The maximum value can be excluded so that the following examples are equal:

SemVerRange.new(">= 1.0.0 < 2.0.0")
SemVerRange.new(SemVer.new("1.0.0"), SemVer.new("2.0.0"), true) 

Unlike the SemVer type, you cannot paramatize the SemVerRange type, as it represents all semantic version ranges.