Skip to content

Instantly share code, notes, and snippets.

@dontlaugh
Last active June 4, 2022 19:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dontlaugh/ca64293f3e5138b2d03125d7300f5c9c to your computer and use it in GitHub Desktop.
Save dontlaugh/ca64293f3e5138b2d03125d7300f5c9c to your computer and use it in GitHub Desktop.
CUE language basics

Download the cue cli tool and follow along

CUE Basics

When CUE is exported to JSON, every value in every (processed) file is unified into one giant object.

% echo 'name: "Vlad"' > /tmp/name.cue
% echo 'disposition: "cheerful"' > /tmp/disposition.cue
% cue export /tmp/name.cue /tmp/disposition.cue 
{
    "name": "Vlad",
    "disposition": "cheerful"
}

Note: export takes one or more cue files and renders them to JSON

CUE is a superset of JSON. So it must render to either a top-level array, or a top-level object, but not both at the same time. Otherwise, unification fails.

% echo '[1, 2, 3]' > /tmp/array.cue
% echo '{"key": "value"}' > /tmp/object.cue
% cue export /tmp/array.cue /tmp/object.cue 
conflicting values [1,2,3] and {key:"value"} (mismatched types list and struct):
    ../../../../tmp/array.cue:1:1
    ../../../../tmp/object.cue:1:1

If you want to use hyphens in your keys, they must be quoted.

% echo 'works_fine: true' > /tmp/unquoted_key.cue
% echo '"needs-quotes": true' > /tmp/quoted_key.cue
% cue export /tmp/unquoted_key.cue /tmp/quoted_key.cue 
{
    "works_fine": true,
    "needs-quotes": true
}

Tip: try unquoting the key with the hyphen

Unification doesn't just unify across files, it is also a global merge of all types and values. The following fails, because the types are different.

% echo 'foo: "baz"' > /tmp/string_value.cue
% echo 'foo: 100' > /tmp/integer_value.cue
% cue export /tmp/string_value.cue /tmp/integer_value.cue 
foo: conflicting values "baz" and 100 (mismatched types string and int):
    ../../../../tmp/integer_value.cue:1:6
    ../../../../tmp/string_value.cue:1:6

But even if we quote the integer, it still fails, because the values conflict and there is no way to unify everything into a top-level object.

% echo 'foo: "baz"' > /tmp/string_value.cue
% echo 'foo: "100"' > /tmp/integer_value.cue  # a string now
% cue export /tmp/string_value.cue /tmp/integer_value.cue
foo: conflicting values "100" and "baz":
    ../../../../tmp/integer_value.cue:1:6
    ../../../../tmp/string_value.cue:1:6

Types and Values, export vs. eval

The export command unifies a bunch of files and emits a data format. We've seen JSON, but you can export YAML, too.

% echo "primes: [1,2,3,5]" | cue export --out yaml - 
primes:
- 1
- 2
- 3
- 5

The eval command is different. It unifies and emits CUE itself.

% echo 'primes: [1,2,3,5], name: "carlos"' | cue eval -
primes: [1, 2, 3, 5]
name: "carlos"

Tip: Multiple key-value pairs on one line is valid if they're comma-separated

One of the uses of eval is transforming JSON into CUE.

% echo '{"primes": [1,2,3,5], "name": "carlos"}' | cue eval -
primes: [1, 2, 3, 5]
name: "carlos"

The eval command is also used for debugging the more advanced concepts of CUE. Here we unify a type constraint on the key foo, with a concrete string value "baz".

% echo 'foo: string, foo: "baz"' | cue eval -        
foo: "baz"

As you can see, CUE's unification will prefer concrete values when emitting CUE.

Importantly, concrete values are required for export! This export fails, because we don't provide a concrete value for the "age" key in our schema.

% echo 'name: string, age: int' > /tmp/schema.cue
% echo 'name: "Natasha"' > /tmp/concrete_values.cue
% cue export /tmp/concrete_values.cue /tmp/schema.cue 
age: incomplete value int

But eval succeeds. The eval unification prefers concrete values but does not require them.

% cue eval /tmp/concrete_values.cue /tmp/schema.cue 
name: "Natasha"
age:  int

CUE is all about defining schemas, after all.

Setting default values

Default values are marked with an asterisk.

// Port is either some integer, or 8080 if not provided
port: int | *8080

Save that file to /tmp/default_values.cue, then export it to render the default value.

% cue export /tmp/default_values.cue
{
    "port": 8080
}

Constraining values, enum-style

Specific concrete values of a field can be constrained like this

// Must be one of these values
severity: "high" | "medium" | "low"
% echo 'severity: "high" | "medium" | "low"' > /tmp/severity.cue
% echo 'severity: "unknown"' | cat /tmp/severity.cue - | cue eval -
severity: 3 errors in empty disjunction:
severity: conflicting values "high" and "unknown":
    -:1:11
    -:2:11
severity: conflicting values "low" and "unknown":
    -:1:31
    -:2:11
severity: conflicting values "medium" and "unknown":
    -:1:20
    -:2:11

CUE calls these disjunctions

Defining Variables

CUE has "definitions", and you can use them like you would variable declarations in other languages.

#DashboardPort: 1337

configs: {
    host: "localhost"
    port: #DashboardPort
}

But definitions are also for defining struct types.

#Address: {
    street: string
    city:   string
    // postal_code is optional
    postal_code?:   string
}

Type constraints with Structs

We can use the & symbol to apply a type definition to some concrete values, and CUE will make sure we don't pass any illegal values.

Let's use that #Address definition from before.

// File: /tmp/destinations.cue

#Address: {
    street: string
    city:   string
    // postal_code is optional
    postal_code?:   string
}

white_house: #Address & {
    street: "1600 Penn. Ave."
    city:   "Washington"
}

louvre_museum: #Address & {
    street:      "99 rue de Rivoli"
    city:        "Paris"
    postal_code: "75001"
}

And note that postal_code is omitted from export, if not provided.

% cue export /tmp/destinations.cue
{
    "white_house": {
        "street": "1600 Penn. Ave.",
        "city": "Washington"
    },
    "louvre_museum": {
        "street": "99 rue de Rivoli",
        "city": "Paris",
        "postal_code": "75001"
    }
}

Interestingly, optional fields are also omitted from eval, if not required.

% cue eval /tmp/destinations.cue
#Address: {
    street: string
    city:   string
}
white_house: {
    street: "1600 Penn. Ave."
    city:   "Washington"
}
louvre_museum: {
    street:      "99 rue de Rivoli"
    city:        "Paris"
    postal_code: "75001"
}
@lucasmoten
Copy link

Great gist. In section #setting-default-values, recommend

echo 'port: int | *8080' > /tmp/default_values.cue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment