Search code examples
goyamlcuelang

Using cuelang, how to place conditions on a struct field of a cue schema that was generated from a Golang module


I'd like to have additional constraints on certain struct fields in a cue schema that was generated from a Golang module.

schema.cue below is generated from a corresponding Golang module

package app

#App: {
  version: string @go(Version)
}
...

I have tried specifying cue annotations in the source Golang module (example below) but that wasn't yielding the right constraints in the generated schema.cue.

type App struct {
    Version         string    `cue:"'version: =~core.myapp.dev/v1'" yaml:"Version"`
}

Another approach I tried was using conjuctions to extend an instance of the schema.

package app

#App: {
  version: string @go(Version)
}
...

#AppExtended: #App & {
  version: =~ "core.myapp.dev/v1"
}

However, cue vet schema.cue tests/badVersionValue.yaml, wasn't yielding any errors to stdout, where badVersionValue.yaml contained Version: badstring.

I am able to have a working solution with the following manually written schema.cue

package app

version: =~"core.myapp.dev/v1"

such that cue vet schema.cue tests/badVersionValue.yaml yields

Version: invalid value "badstring" (out of bound =~"core.myapp.dev/v1"):
...

The downside of this approach is I would then be writing cue schema files from scratch rather than using and iterating upon the generated ones from the Golang source code/source of truth.


Solution

  • I misunderstood how to properly vet with cue when definitions are involved.

    Based on this example from cuetorials.com you can specify -d "#Schema" where

    The extra -d "<path>" tells Cue the value we want to use to validate each Yaml entry against. We have to do this because Cue defaults to using top-level information.

    I'm seeing success not only with the following where the yaml file contents are Version: 5.

    cue vet badVersionStr.yaml schema.cue -d '#App'                         
    Version: conflicting values 5 and string (mismatched types int and string):
    

    but also with appending constraints to existing fields, per the OP:

    package app
    
    #App: {
      version: string @go(Version)
      version: =~ "core.myapp.dev/v1"
    }
    ...
    

    which now yields the expected output

    cue vet badVersionStr.yaml schema.cue -d '#App'                         
    Version: invalid value "badString" (out of bound =~"core.myapp.dev/v1"):
    

    EDIT: An even better approach for folks using a Golang module for everything. You can place constraints as struct field annotations in the Golang module.

    # app.go
    type App struct {
       Version     string     `cue:"=~\"core.myapp.dev/v1\"" yaml:"Version"`
    ...
    

    With cue get go app.go you get a corresponding app_go_gen.cue that should look like this

    #App: {
        Version:     string & =~"core.myapp.dev/v1" @go(Version)
    ...
    

    Which works when doing the cue vet ... commands referenced above. This means you only need to specify the constraints in the Golang module and then generate the Cue schema which will interpret those annotations as constraints on the struct field.