CUE Configuration Language

repository·master·Indexed 11 days ago

https://github.com/cue-lang/cue

A powerful configuration language designed to validate data, write schemas, and ensure configurations align with defined policies. CUE integrates with common formats including JSON, YAML, TOML, XML, OpenAPI, Protobuf, and JSON Schema. The toolset includes a CLI for exporting, evaluating expressions, and validating data against schemas.

Tokens
49.5K
Snippets
189
Records
236
Agent score
89%

What's inside CUE

  1. What is CUE?

    master

    CUE (pronounced 'cue' or 'Q') is a general-purpose, strongly typed, constraint-based language. It is designed for managing and reasoning over structured data, making it suitable for:

    • Data templating
    • Data validation
    • Code generation
    • Scripting
    • Cloud configuration

    CUE derives its formalism from relational programming languages, allowing for straightforward management of large datasets. The CUE tooling provides a scripting language for creating scripts and simple servers, also expressed in CUE.

  2. Understand CUE source code representation

    master

    CUE source code is represented as Unicode text encoded in UTF-8.

    Key characteristics of CUE source text:

    • Non-canonicalization: Accented characters are treated as distinct code points. For example, a single accented code point is different from a character constructed using a combining accent and a letter.
    • Case sensitivity: Upper and lower case letters are distinct characters.
    • NUL character: Compilers may disallow the NUL character (U+0000) for compatibility.
    • Byte Order Mark (BOM): A UTF-8-encoded BOM (U+FEFF) may be ignored if it is the first code point, but may be disallowed elsewhere.
  3. Use embeddings to merge struct values

    master

    An embedded value is an operand used as a declaration within a struct. If the embedded value is a struct, it is unified with the enclosing struct.

    Key behaviors:

    • Bypassing Closure: Embedding allows you to bypass the restrictions of closed structs. If an embedding resolves to a closed struct, the enclosing struct becomes closed, but it may still contain fields that would otherwise be disallowed by standard closure rules.
    • Non-struct embeddings: If an embedded value is not a struct, the enclosing struct may only contain definitions (#) or hidden fields (_), not regular fields.
    S1: {
        a: 1
        b: 2
        { // This is an embedded value
            c: 3
        }
    }
    // S1 is { a: 1, b: 2, c: 3 }
    
    S2: close({
        a: 1
        b: 2
        { 
            c: 3
        }
    })
    // S2 is equivalent to close(S1)
  4. Define required and optional field constraints

    master

    You can declare field constraints in a struct using markers. These markers do not change the field name but define how the field must behave during unification.

    • Required (!): A required field must be present and must unify with the constraint. A required field with a bottom value makes the whole struct bottom.
    • Optional (?): An optional field may or may not be present. If an optional field's constraint evaluates to bottom, it does not invalidate the struct as long as the field is not actually defined (i.e., it remains omitted).

    Subsumption Hierarchy: {a: x} ⊑ {a!: x} ⊑ {a?: x} (A concrete value satisfies a required constraint, which in turn satisfies an optional constraint.)

    // Required vs Optional
    {foo!: 3} & {foo: 3}    // Result: {foo: 3}
    {foo?: 1} & {foo?: 2}   // Result: {foo?: _|_} (This is valid/fine because foo is optional)
    {foo!: 1} & {foo?: 2}  // Result: _|_ (Conflict because foo is required)
    
    // Type constraints
    {foo!: int} & {foo: <=3} // Result: {foo!: <=3}
  5. Use comments in CUE

    master

    Comments are used for program documentation. CUE supports line comments starting with // that continue until the end of the line. A comment cannot start inside a string literal or inside another comment, and it acts like a newline.

    // This is a line comment
  6. Apply bounds to values

    master

    A bound is a unary expression that defines a range of values using comparison operators (e.g., >= 2). In CUE, bounds act as a logical disjunction of all values that satisfy the comparison.

    You can use bounds to constrain values during unification.

    // Constraining a value within a range
    2 & >=1 & <=5           // Result: 2
    2.5 & int & >1 & <5     // Result: _|_ (bottom, because 2.5 is not an int)
    >=0 & <=7 & >=3 & <=10  // Result: >=3 & <=7
  7. Understand CUE blocks and scoping

    master

    CUE uses lexical scoping based on blocks. A block is a sequence of declarations enclosed in braces { ... } or implicit in certain clauses.

    Types of blocks:

    • Universe block: Encompasses all CUE source text.
    • Package block: Contains all source text in a specific package.
    • File block: Contains all source text in a single file.
    • Struct literal block: The scope inside { ... }.
    • Implicit blocks: Created by for and let clauses in comprehensions.

    Scoping Rules:

    1. Predeclared identifiers: Scoped to the universe block.
    2. Top-level fields: Scoped to the package block.
    3. Top-level aliases/let identifiers: Scoped to the file block.
    4. Imported package names: Scoped to the file containing the import.
    5. Identifiers inside a struct: Scoped to the innermost containing block.

    An identifier declared in an inner block can shadow an identifier in an outer block.

  8. Use the `incorrect` modifier to document known-incorrect behavior

    master

    The incorrect positional flag can be added to any assertion directive (e.g., eq, err, kind, closed). It marks an assertion as documenting the current known-incorrect behavior of a field.

    • If the assertion passes: The test does NOT fail. The runner logs a NOTE stating that the documented incorrect behavior is still present.
    • If the assertion fails: The test DOES FAIL. This indicates the behavior has changed, which may be a fix or a new regression.

    It is commonly used alongside a :todo directive to document what the correct behavior should eventually be.

    // Documents that the field currently produces 42 (wrong), and that it
    // should eventually produce an error.
    x: 42 @test(eq, 42, incorrect) @test(err:todo, p=1, code=eval)
  9. Set fix priority with the `p=N` modifier

    master

    Any :todo directive (such as eq:todo, err:todo, or @test(todo)) can include a p=N key-value argument to indicate the priority of the fix. This is purely informational and is included in log output; it does not affect pass/fail behavior.

    • p=0: Critical (must fix immediately)
    • p=1: Important (fix soon)
    • p=2: Good to have (fix when convenient)

    Higher integers indicate lower urgency.

    x: 42 @test(eq:todo, 99, p=1)
    x: 1/0 @test(err:todo, p=0, code=eval)
    result: bad @test(eq, bad) @test(todo, p=2, why="low-priority cleanup")
  10. Unification in CUE using the `&` operator

    master

    Unification is the process of finding the greatest lower bound of two values a and b. In CUE, this is performed using the binary expression a & b.

    Properties:

    • Commutative: a & b == b & a
    • Associative: (a & b) & c == a & (b & c)
    • Idempotent: a & a == a
    • Order of evaluation: Because of the properties above, the order in which you unify values does not change the result.

    Unification Rules:

    • The unification of a with itself is a.
    • If a ⊑ b, then a & b is a.
    • The unification of any value with bottom (_|_) is bottom.
    ({a:1} | {b:2}) & {c:3}   // Result: {a:1, c:3} | {b:2, c:3}
    (int | string) & "foo"    // Result: "foo"
    ("a" | "b") & "c"         // Result: _|_
  11. Understanding structural cycles and validity

    master

    A structural cycle occurs when a node references one of its ancestor nodes.

    • Cyclic Nodes: If a node a references an ancestor, a and all its descendants are considered cyclic.
    • Validity Rule: A node x composed of conjuncts c1 & ... & cn is valid only if at least one of its conjuncts is not cyclic. If all conjuncts are cyclic, the value is invalid and eliminated (often from a disjunction).

    Examples of disallowed structural cycles:

    • Infinite lists where all elements are defined recursively.
    • Infinite nested structures (e.g., a -> b -> c -> a).

    Example of a valid recursive structure (using disjunction): By using a disjunction (|), the recursive reference becomes part of a choice. If the recursive path is cyclic, it is eliminated, leaving the non-cyclic path as the valid value.

    // Disallowed: infinite structure
    #List: {
        head: 1
        tail: #List
    }
    
    // Allowed: recursive reference via disjunction
    #List: {
        head: _
        tail: null | #List
    }
    
    // Usage: the tail in the deepest element becomes `null` 
    // because the cyclic conjunct is eliminated.
    MyList: #List & { head: 1, tail: { head: 2 }}
  12. Use selectors to access struct fields and handle defaults

    master

    Selectors allow you to access fields within a struct. If the field exists, the selector returns its value. If the field does not exist, the expression results in bottom (_|_).

    When using selectors on values with associated defaults, CUE selects the default value if the field is not explicitly present. This behavior applies to disjunctions as well: the selector is applied to each element in the disjunction.

    Note that for quoted field names containing special characters (like hyphens), you must use string selector notation.

    T: {
        x:     int
        y:     3
        "x-y": 4
    }
    
    a: T.x     // int
    b: T.y     // 3
    c: T.z     // _|_ (field 'z' not found)
    d: T."x-y" // 4
    
    e: {a: 1|*2} | *{a: 3|*4}
    f: e.a  // 4 (default value selected)