I want to ensure that my users only set one of two fields:
rotations:
- type: weekly
time_restrictions:
# Allow only ONE of the following fields:
weekday_time_of_day: {...}
time_of_day: [...]
I came across the OneOf
pattern on Cuetorials, but this does only seem to help when wanting to enforce a schema while writing cue files.
#OneOfTimeRestrictions: {time_of_day: [...string]} | {weekday_time_of_day: [...string]}
rotations: [{
type: *"weekly" | "daily"
restrictions: #OneOfTimeRestrictions | {} // won't work, naturally, because nothing is "chosen"
}]
(The values of the mutually exclusive fields are actually additional, more complex structs, not strings, in case that might matter - but for the sake of a shorter example I've omitted them.)
However, I'm trying to vet YAML instead.
The problem is that when defining this:
#OneOfTimeRestrictions: rotations: [{
type: *"weekly" | "daily"
restrictions: {time_of_day: [...string]} | {weekday_time_of_day: [...string]}
}]
Both fields are acceptable, including when giving them at the same time.
Pointers?
Not a cue expert but,
in CUE {}
can be a closed or an open structure depending on the context.
#A: {}
is a closed schema, meaning you can't add new field to it.
While B:{}
is an open value where you can add new field to it.
#A:{}
a: a & {someKey: true} // will produce an error
B:{}
b: b & {someKey: true} // will pass
_C:{}
c: _C & {someKey: true} // will pass
(You can test it here: https://cuelang.org/play/?id=XigxaAJ1bcp#cue@export@cue)
(And read more about it here: https://cuetorials.com/deep-dives/closedness/)
So in your first CUE code example, the line restrictions: #OneOfTimeRestrictions | {}
says restrictions
must match either a closed schema with either a weekday_time_of_day
or time_of_day
field and nothing else or must match an unclosed {}
which will match every non-null object.
In your second code example, you are saying restictions
as to match to an open structure that has the weekday_time_of_day
field or match to an open structure that has the time_of_day
field. So, they are basically identical.
Try it like this
#OneOfTimeRestrictions: {time_of_day: [...string]} | {weekday_time_of_day: [...string]}
#Restrictions: {
restrictionA: string
restrictionB: bool
// ...
restrictionZ: bool
#OneOfTimeRestrictions
}
rotations: [...{
type: *"weekly" | "daily"
restrictions: #Restrictions
}]
Or if you don't like the additional schema, like this
#OneOfTimeRestrictions: {time_of_day: [...string]} | {weekday_time_of_day: [...string]}
rotations: [...{
type: *"weekly" | "daily"
restrictions: {
restrictionA: string
restrictionB: bool
// ...
restrictionZ: bool
#OneOfTimeRestrictions
}
}]
But these solutions will make the objects in relations
closed. So, you will have deliberately defined every additional restriction.
(I'm not certain why restrictions
is close in my second example, but in my test it is closed).
If you need that the objects inside relations
are open, you can use something like this:
rotations: [...{
type: *"weekly" | "daily"
restrictions: {
time_of_day: [...string]
weekday_time_of_day: [...string]
_tr1: bool | *false
_tr2: bool | *false
if time_of_day == [] {
_tr1: true
}
if weekday_time_of_day == [] {
_tr2: true
}
_time_restiction_valid: true
_time_restiction_valid: (_tr1 && !_tr2) || (!_tr1 && _tr2) // append '|| (!_tr1 && !_tr2)' if you want to allow that nither can be set
}
}]