§ A7 · Track A · CUE from zero

Required, optional, regular

Decide for each field whether it must be supplied, may be supplied, or is a plain field.

Time
8 min
Level
beginner
Needs
A6
You will learn
What happens when a field is never set? How do the marks ! and ? change the answer?
Done when
pravo eval -c names the missing required field and passes once you set it.

View as Markdown

On this page

HCL answers "what if nobody sets it?" with three different mechanisms.

variables.tf
variable "bucket" {
  type = object({
    name        = string
    description = optional(string)
    versioning  = optional(bool, false)
  })
}

An attribute without optional() must be supplied. An omitted description becomes null. An omitted versioning becomes false.

In CUE, a field is one of three kinds. A regular field has no mark. A field written name! is a required field: someone must supply a value. A field written name? is an optional field: it may stay absent.

Warm-up

From earlier lessons

A bucket name must match =~"^[a-z0-9-]{3,63}$", and a second line sets it to "Logs". Is the value accepted?

No. The capital letter does not match the regular expression (A5).

A field is name: string and nothing else is written. Does pravo eval fail?

No. A type is a value that is not finished yet (A2). The flag -c asks for finished values and fails.

One bucket, three kinds

Work in an empty directory. Write estate.cue. Each kind appears once, and no field has a value.

estate.cue
package estate

bucket: {
	versioning:   bool
	name!:        string
	description?: string
}

Decide for each field, then open the answer.

Predict Which of the three fields does pravo eval print?

Two. The plain field and the required field are printed. The optional field is left out.

terminal
$pravo eval
bucket: {
	versioning: bool
	name!:      string
}

The optional field exists in the specification. pravo eval hides it while it has no value. The flag -O prints it.

terminal
$pravo eval -O
bucket: {
	versioning:   bool
	name!:        string
	description?: string
}

The marks are part of the output. name! and description? keep their marks until a value arrives.

Break it

Ask for finished values with -c.

terminalexit 1
$pravo eval -c
Error: the value is not concrete:
bucket.name: field is required but not present:
    ./estate.cue:5:2
bucket.versioning: incomplete value bool:
    ./estate.cue:4:16

Two fields are reported. The optional field is not.

  • bucket.name: field is required but not present is the required field. The location is the line that declares name!. It marks the demand, not the place where the value belongs.
  • bucket.versioning: incomplete value bool is the plain field. It is still a type (A2).
  • description? is absent. An absent optional field is not an error.

Here is the table that answers the question for all three kinds.

Never set Plain Required Optional
Written as x: string x!: string x?: string
pravo eval prints x: string x!: string nothing
pravo eval -O prints x: string x!: string x?: string
pravo eval -c fails: incomplete value fails: required but not present no error

Once a value is set, all three kinds print x: "a". C2 lists every cause of an incomplete value.

Supply the values

Write a value for each field that needs one.

estate.cuevalid
package estate

bucket: {
	versioning:   bool
	name!:        string
	description?: string
}

bucket: {
	name:       "logs"
	versioning: true
}
terminal
$pravo eval -c
bucket: {
	versioning: true
	name:       "logs"
}
terminal
$pravo eval -O
bucket: {
	versioning:   true
	name:         "logs"
	description?: string
}

The flag -c passes. The optional field stays absent, because nobody set it. Writing description: "Access logs" makes it appear in pravo eval.

Which kind to write

You want Write In HCL
The caller must supply it name!: string an attribute without optional()
The caller may supply it description?: string optional(string), which gives null
A value in every case versioning: *false | bool optional(bool, false)

The third row is a plain field with a default (A6). It always has a value, so -c never fails on it.

Exercise

Exercise

Require an owner

Replace estate.cue with this file. Both owner and description are plain fields, so -c reports both.

estate.cue
package estate

bucket: {
	name:        "logs"
	owner:       string
	description: string
}
terminalexit 1
$pravo eval -c
Error: the value is not concrete:
bucket.description: incomplete value string:
    ./estate.cue:6:15
bucket.owner: incomplete value string:
    ./estate.cue:5:15

Mark owner as required and description as optional. Then pravo eval -c fails only for owner, and it passes once owner has a value.

The check runs the file as it is, and again with an owner supplied from a second file.

check.sh
tmp=$(mktemp -d); cp estate.cue "$tmp/"
printf 'package estate\n\nbucket: owner: "platform"\n' > "$tmp/fixture.cue"
pravo eval -c 2>&1 | grep -q 'owner: field is required but not present' || { echo "owner should be required"; exit 1; }
if pravo eval -c 2>&1 | grep -q 'description'; then echo "description should be optional"; exit 1; fi
pravo -C "$tmp" eval -c > /dev/null 2>&1 || { echo "with an owner, -c should pass"; exit 1; }
pravo -C "$tmp" eval -O | grep -q 'description?: string' || { echo "description should stay optional"; exit 1; }
rm -rf "$tmp"
echo ok

Run it on the start state. It fails.

terminalexit 1
$sh check.sh
owner should be required
Hint 1

Put ! after the field name for a required field. Put ? after the field name for an optional one. The colon stays.

Hint 2

See required field and optional field. Neither mark changes the type after the colon.

Solution
estate.cue
package estate

bucket: {
	name:         "logs"
	owner!:       string
	description?: string
}
terminalexit 1
$pravo eval -c
Error: the value is not concrete:
bucket.owner: field is required but not present:
    ./estate.cue:5:2

Recap

  • A plain field may stay a type, and -c reports it as incomplete.
  • A required field name! must be supplied, and -c reports it if it is not.
  • An optional field name? may stay absent, and -O prints it.

Check yourself

Which fields does pravo eval -c report when nothing sets them?

Plain fields that are still types, and required fields. Optional fields are never reported.

Where does an optional field appear in pravo eval output when it has no value?

Nowhere. Only pravo eval -O prints it, as description?: string.

The next lesson makes a shape that refuses fields it does not declare.