Values
Every value in CASC has a type the parser knows. Numbers, durations, sizes, dates and addresses arrive typed — nothing has to be parsed a second time by the application.
At a glance
| Type | Examples |
|---|---|
| nil, boolean | nil true false |
| integer | -17 1_000_000 0xDEAD_BEEF 0o755 0b1101 |
| float | 3.1415 5e+22 -2E-2 inf -inf |
| atom | info :info enabled? __sentry__ :inf |
| string | "escaped\n" 'literal' """multi-line""" |
| date, time | 1979-05-27 07:32:00 1979-05-27T07:32:00Z |
| IP address | 127.0.0.1 10.0.0.0/8 ::1 fe80::1/64 |
| duration | 500ms 1h30m 1.5h 2d 1_000ms |
| byte size | 512MiB 1.5GiB 10GB |
| list | [1, 2, 3] ["a" "b"] [{ path = "/" }] |
| tuple | (52.52, 13.405) |
Nil and booleans
nil, true and false are keywords. There is no null, yes or on: those are ordinary atoms.
flags = [nil, true, false, null, yes]
%{"flags" => [nil, true, false, :null, :yes]}Numbers
Integers take an optional sign and may be written in hexadecimal (0x), octal (0o) or binary (0b). Floats need digits on both sides of the point or an exponent; 1e3 is a float. _ separates digit groups anywhere. inf, +inf and -inf are the infinities.
n = [-17, +99, 0xDEAD_BEEF, 0o755, 0b1101, 1_000_000, 3.1415, 5e+22, -2E-2, inf, -inf]
%{"n" => [-17, 99, 3735928559, 493, 13, 1000000, 3.1415, 5.0e22, -0.02,
%Cooper.Infinity{sign: :positive}, %Cooper.Infinity{sign: :negative}]}Atoms
A bare word in value position is an atom — a symbolic name, the way log levels and modes are usually written. A leading : is optional, and required to turn a reserved word into an atom.
levels = [info, :info, enabled?, ready!, handle-otp, __sentry__, :inf, :nil]
%{"levels" => [:info, :info, :enabled?, :ready!, :"handle-otp", :__sentry__,
:inf, nil]}:inf is an ordinary atom. :nil, :true and :false are the values nil, true and false — on the BEAM those atoms are the values, and every implementation reads them that way, so a document means the same everywhere.
Strings
Three forms, for three jobs:
"double"— escapes\n \r \t \" \\ \uXXXX, and interpolation of@{},${},%{},!{}and!tag(…).'single'— literal: no escapes, no interpolation. Good for patterns and anything full of backslashes."""triple"""— multi-line. The newline after the opening quotes is dropped and the common indentation removed, so the text can be indented with the file.
@who = "world"
greeting = "hello, @{who}\n"
pattern = '^\d+ @{not interpolated}$'
motd = """
Welcome to the service.
Status: operational
"""%{"greeting" => "hello, world\n",
"pattern" => "^\\d+ @{not interpolated}$",
"motd" => "Welcome to the service.\n Status: operational\n"}Dates and times
Unquoted ISO 8601: a date, a time with seconds (fractions allowed), or both joined by T. A date-time with Z or an offset is an instant, normalised to UTC; without one it is a local date-time.
at = [1979-05-27T07:32:00Z, 1979-05-27T07:32:00.999+02:00, 1979-05-27T07:32:00, 1979-05-27, 07:32:00.5]
%{"at" => [~U[1979-05-27 07:32:00Z], ~U[1979-05-27 05:32:00.999Z],
~N[1979-05-27 07:32:00], ~D[1979-05-27], ~T[07:32:00.5]]}A date that does not exist, such as 2023-02-30, is a load error.
IP addresses
IPv4 and IPv6 addresses are values, unquoted, each with an optional /prefix for a network. An octet above 255 is a load error, not a string.
allow = [127.0.0.1, 10.0.0.0/8, ::1/128, fe80::1]
%{"allow" => [#Cooper.IPv4<127.0.0.1>, #Cooper.IPv4<10.0.0.0/8>,
#Cooper.IPv6<::1/128>, #Cooper.IPv6<fe80::1>]}Durations
A number followed directly by a unit: ns, us (or µs), ms, s, m, h, d. Units combine from largest to smallest, each once, with whole numbers; a single unit may have a fraction. _ separates digit groups, as in any number. The value is computed exactly, in nanoseconds.
timeout = 500ms window = 1h30m same = 1.5h retention = 2d grouped = 1_000ms
%{"timeout" => %Cooper.Duration{nanoseconds: 500000000},
"window" => %Cooper.Duration{nanoseconds: 5400000000000},
"same" => %Cooper.Duration{nanoseconds: 5400000000000},
"retention" => %Cooper.Duration{nanoseconds: 172800000000000},
"grouped" => %Cooper.Duration{nanoseconds: 1000000000}}30m1h (ascending), 1h1h (repeated) and 1.5h30m (a fraction in a compound) are load errors.
Byte sizes
Decimal units B kB MB GB TB PB count in thousands, binary units KiB MiB GiB TiB PiB in 1024s. Units are case-insensitive; fractions are allowed.
sizes = [512MiB, 1.5GiB, 10GB, 1kib]
%{"sizes" => [%Cooper.ByteSize{bytes: 536870912},
%Cooper.ByteSize{bytes: 1610612736},
%Cooper.ByteSize{bytes: 10000000000},
%Cooper.ByteSize{bytes: 1024}]}Lists and tuples
A list […] holds any values; commas, spaces and newlines all separate elements, in any mix. A tuple (…) is a fixed group — a coordinate, a pair — and is always replaced as a whole, never merged.
hosts = [ "a.internal" "b.internal", "c.internal" ] office = (52.5200, 13.4050) matrix = [[1, 2], (3, 4)]
%{"hosts" => ["a.internal", "b.internal", "c.internal"],
"office" => {52.52, 13.405},
"matrix" => [[1, 2], {3, 4}]}Maps in a list
An element of a list or tuple may be a block — a map, written as a block is anywhere else. A list of maps is still a list: it is replaced as a whole, and + and - append or remove whole maps, compared by value.
access_control = [
{ path = "^/admin", roles = ["ROLE_ADMIN"] }
{ path = "^/api", roles = ["ROLE_API"] }
]
+access_control = [{ path = "^/", roles = [] }]%{"access_control" => [
%{"path" => "^/admin", "roles" => ["ROLE_ADMIN"]},
%{"path" => "^/api", "roles" => ["ROLE_API"]},
%{"path" => "^/", "roles" => []}]}