§ A8 · Track A · CUE from zero
Definitions reject typos
Write a shape once, build values from it, and let the shape refuse every field it does not declare.
On this page
HCL checks the arguments of a resource against the provider schema. A convention of your own has no schema. Every bucket must carry an owner and a cost_center. The rule becomes a validation block on each variable that must follow it.
variable "tags" {
type = map(string)
validation {
condition = alltrue([for k in ["owner", "cost_center"] : contains(keys(var.tags), k)])
error_message = "tags must contain owner and cost_center."
}
}
The block checks that two keys are present. It does not reject a key it does not list.
CUE has the same gap until you write a shape. Here is a bucket with a misspelt field. Work in an empty directory and write estate.cue.
package estate
logs: {name: "logs", versioning: false, vrsioning: true}
Read the file once more. The intended field is versioning.
Predict Does pravo eval complain about vrsioning?
No. A plain struct accepts any field, so vrsioning is a new field.
logs: {
name: "logs"
versioning: false
vrsioning: true
}
The file is valid and wrong. Versioning stays off, and nothing says so.
A name that starts with # is a definition. A definition is a shape. You build a value from it with &. A definition is closed: a value built from it may contain only the fields that the definition declares.
Warm-up
From earlier lessons
A field is name!: string and nothing sets it. What does pravo eval -c report?
The required field: field is required but not present (A7).
A field is versioning: *false | bool, and a second line sets it to "yes". Is the value accepted?
No. The text "yes" is a string, and neither choice is a string (A6).
Build values from a shape
Replace the contents of estate.cue. The shape declares three fields.
package estate
#Bucket: {
name!: string
class: *"standard" | "ia" | "glacier"
versioning: *false | bool
}
logs: #Bucket & {name: "logs"}
media: #Bucket & {name: "media", versioning: true}
logs: {
name: "logs"
class: "standard"
versioning: false
}
media: {
name: "media"
class: "standard"
versioning: true
}
The definition #Bucket is not printed. It describes a shape and holds no data. The two buckets take the defaults of the shape.
Break it
Misspell the field again. This time the bucket is built from #Bucket.
package estate
#Bucket: {
name!: string
class: *"standard" | "ia" | "glacier"
versioning: *false | bool
}
logs: #Bucket & {name: "logs", vrsioning: true}
media: #Bucket & {name: "media", versioning: true}
Error: evaluating .:
logs.vrsioning: field not allowed:
./estate.cue:9:33
Read the error from the top.
- The path is
logs.vrsioning. - The rule is
field not allowed.#Bucketis closed and does not declarevrsioning. - One location is given, line 9. Only that line wrote the field.
pravo prints no buckets at all. A specification with an error has no result, so the typo cannot reach a plan.
When a definition refuses a field you meant to add, read C3. It shows how to open a definition with ... and how to extend one by embedding.
Exercise
Tag every bucket
Replace estate.cue with this file. It has the typo and no tags.
package estate
#Bucket: {
name!: string
class: *"standard" | "ia" | "glacier"
versioning: *false | bool
}
logs: #Bucket & {name: "logs", vrsioning: true}
media: #Bucket & {name: "media"}
backups: #Bucket & {name: "backups", class: "glacier"}
Error: evaluating .:
logs.vrsioning: field not allowed:
./estate.cue:9:35
Do three things.
- Fix the typo.
- Define
#Taggedwith two required fields,ownerandcost_center. - Give
#Bucketa fieldtagsof that shape. Set both tags on all three buckets.
Then pravo eval -c passes. A bucket without tags fails pravo eval -c. A misspelt tag is refused by pravo eval.
The check adds four more buckets to a copy of the file, one at a time. One is complete, one lacks a tag, one has a misspelt tag and one has no tags.
pravo eval -c > /dev/null 2>&1 || { echo "pravo eval -c should pass"; exit 1; }
tmp=$(mktemp -d)
fixture() {
mkdir "$tmp/$1"; cp estate.cue "$tmp/$1/"
printf 'package estate\n\nextra: #Bucket & {name: "extra"%s}\n' "$2" > "$tmp/$1/fixture.cue"
pravo -C "$tmp/$1" eval -c 2>&1
}
fixture ok ', tags: {owner: "o", cost_center: "c"}' > /dev/null || { echo "a complete bucket should pass"; exit 1; }
fixture one ', tags: {owner: "o"}' | grep -q 'field is required but not present' || { echo "a missing tag should fail"; exit 1; }
fixture typo ', tags: {owner: "o", cost_center: "c", ownr: "x"}' | grep -q 'field not allowed' || { echo "a misspelt tag should be refused"; exit 1; }
fixture none '' | grep -q 'field is required but not present' || { echo "a bucket without tags should fail"; exit 1; }
rm -rf "$tmp"
echo ok
Run it on the start state. It fails.
pravo eval -c should pass
Hint 1
A field can hold a definition as its value: tags: #Tagged. The two tag fields need the mark !.
Hint 2
See definition and closed. A shape inside a shape is closed as well.
Solution
package estate
#Tagged: {
owner!: string
cost_center!: string
}
#Bucket: {
name!: string
class: *"standard" | "ia" | "glacier"
versioning: *false | bool
tags: #Tagged
}
logs: #Bucket & {
name: "logs"
versioning: true
tags: {owner: "platform", cost_center: "c100"}
}
media: #Bucket & {
name: "media"
tags: {owner: "web", cost_center: "c200"}
}
backups: #Bucket & {
name: "backups"
class: "glacier"
tags: {owner: "platform", cost_center: "c100"}
}
name: "backups"
class: "glacier"
versioning: false
tags: {
owner: "platform"
cost_center: "c100"
}
Recap
- A name that starts with
#is a definition: a shape, not data. - A value built with
#Bucket & {...}may contain only declared fields. - A misspelt field is refused during evaluation and names its path.
Check yourself
Why does a plain struct accept vrsioning while #Bucket & {...} refuses it?
A plain struct is open and takes any new field. A definition is closed and allows only the fields it declares.
Does pravo eval print #Bucket?
No. A definition is a shape and is not part of the output. pravo eval -e '#Bucket' prints it.
You have already used a definition. In Start, $local.#Resource.local_file is one that pravo generates from the provider's schema, and rules.cue narrowed it. Every resource is built from such a definition, so a misspelt attribute fails in pravo validate before any provider starts. C3 shows that error on a real resource.