mirror of
https://github.com/mikefarah/yq.git
synced 2024-11-13 22:38:04 +00:00
Added error operator for custom validation #1259
This commit is contained in:
parent
98b411f82e
commit
86c2b03630
56
pkg/yqlib/doc/operators/error.md
Normal file
56
pkg/yqlib/doc/operators/error.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Error
|
||||||
|
|
||||||
|
Use this operation to short-circuit expressions. Useful for validation.
|
||||||
|
|
||||||
|
{% hint style="warning" %}
|
||||||
|
Note that versions prior to 4.18 require the 'eval/e' command to be specified. 
|
||||||
|
|
||||||
|
`yq e <exp> <file>`
|
||||||
|
{% endhint %}
|
||||||
|
|
||||||
|
## Validate a particular value
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
a: hello
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
yq 'select(.a == "howdy") or error(".a [" + .a + "] is not howdy!")' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```bash
|
||||||
|
Error: .a [hello] is not howdy!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validate the environment variable is a number - invalid
|
||||||
|
Running
|
||||||
|
```bash
|
||||||
|
numberOfCats="please" yq --null-input 'env(numberOfCats) | select(tag == "!!int") or error("numberOfCats is not a number :(")'
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```bash
|
||||||
|
Error: numberOfCats is not a number :(
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validate the environment variable is a number - valid
|
||||||
|
`with` can be a convenient way of encapsulating validation.
|
||||||
|
|
||||||
|
Given a sample.yml file of:
|
||||||
|
```yaml
|
||||||
|
name: Bob
|
||||||
|
favouriteAnimal: cat
|
||||||
|
```
|
||||||
|
then
|
||||||
|
```bash
|
||||||
|
numberOfCats="3" yq '
|
||||||
|
with(env(numberOfCats); select(tag == "!!int") or error("numberOfCats is not a number :(")) |
|
||||||
|
.numPets = env(numberOfCats)
|
||||||
|
' sample.yml
|
||||||
|
```
|
||||||
|
will output
|
||||||
|
```yaml
|
||||||
|
name: Bob
|
||||||
|
favouriteAnimal: cat
|
||||||
|
numPets: 3
|
||||||
|
```
|
||||||
|
|
3
pkg/yqlib/doc/operators/headers/error.md
Normal file
3
pkg/yqlib/doc/operators/headers/error.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Error
|
||||||
|
|
||||||
|
Use this operation to short-circuit expressions. Useful for validation.
|
@ -363,6 +363,8 @@ func initLexer() (*lex.Lexer, error) {
|
|||||||
lexer.Add([]byte(`tz`), opToken(tzOpType))
|
lexer.Add([]byte(`tz`), opToken(tzOpType))
|
||||||
lexer.Add([]byte(`with_dtf`), opToken(withDtFormatOpType))
|
lexer.Add([]byte(`with_dtf`), opToken(withDtFormatOpType))
|
||||||
|
|
||||||
|
lexer.Add([]byte(`error`), opToken(errorOpType))
|
||||||
|
|
||||||
lexer.Add([]byte(`toyaml\([0-9]+\)`), encodeWithIndent(YamlOutputFormat))
|
lexer.Add([]byte(`toyaml\([0-9]+\)`), encodeWithIndent(YamlOutputFormat))
|
||||||
lexer.Add([]byte(`to_yaml\([0-9]+\)`), encodeWithIndent(YamlOutputFormat))
|
lexer.Add([]byte(`to_yaml\([0-9]+\)`), encodeWithIndent(YamlOutputFormat))
|
||||||
|
|
||||||
|
@ -91,6 +91,7 @@ var columnOpType = &operationType{Type: "LINE", NumArgs: 0, Precedence: 50, Hand
|
|||||||
|
|
||||||
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 1, Precedence: 50, Handler: collectOperator}
|
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 1, Precedence: 50, Handler: collectOperator}
|
||||||
var mapOpType = &operationType{Type: "MAP", NumArgs: 1, Precedence: 50, Handler: mapOperator}
|
var mapOpType = &operationType{Type: "MAP", NumArgs: 1, Precedence: 50, Handler: mapOperator}
|
||||||
|
var errorOpType = &operationType{Type: "ERROR", NumArgs: 1, Precedence: 50, Handler: errorOperator}
|
||||||
var pickOpType = &operationType{Type: "PICK", NumArgs: 1, Precedence: 50, Handler: pickOperator}
|
var pickOpType = &operationType{Type: "PICK", NumArgs: 1, Precedence: 50, Handler: pickOperator}
|
||||||
var evalOpType = &operationType{Type: "EVAL", NumArgs: 1, Precedence: 50, Handler: evalOperator}
|
var evalOpType = &operationType{Type: "EVAL", NumArgs: 1, Precedence: 50, Handler: evalOperator}
|
||||||
var mapValuesOpType = &operationType{Type: "MAP_VALUES", NumArgs: 1, Precedence: 50, Handler: mapValuesOperator}
|
var mapValuesOpType = &operationType{Type: "MAP_VALUES", NumArgs: 1, Precedence: 50, Handler: mapValuesOperator}
|
||||||
|
20
pkg/yqlib/operator_error.go
Normal file
20
pkg/yqlib/operator_error.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func errorOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||||
|
|
||||||
|
log.Debugf("-- errorOperation")
|
||||||
|
|
||||||
|
rhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.RHS)
|
||||||
|
if err != nil {
|
||||||
|
return Context{}, err
|
||||||
|
}
|
||||||
|
errorMessage := "aborted"
|
||||||
|
if rhs.MatchingNodes.Len() > 0 {
|
||||||
|
errorMessage = rhs.MatchingNodes.Front().Value.(*CandidateNode).Node.Value
|
||||||
|
}
|
||||||
|
return Context{}, fmt.Errorf(errorMessage)
|
||||||
|
}
|
40
pkg/yqlib/operator_error_test.go
Normal file
40
pkg/yqlib/operator_error_test.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package yqlib
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
const validationExpression = `
|
||||||
|
with(env(numberOfCats); select(tag == "!!int") or error("numberOfCats is not a number :(")) |
|
||||||
|
.numPets = env(numberOfCats)
|
||||||
|
`
|
||||||
|
|
||||||
|
var errorOperatorScenarios = []expressionScenario{
|
||||||
|
{
|
||||||
|
description: "Validate a particular value",
|
||||||
|
document: `a: hello`,
|
||||||
|
expression: `select(.a == "howdy") or error(".a [" + .a + "] is not howdy!")`,
|
||||||
|
expectedError: ".a [hello] is not howdy!",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Validate the environment variable is a number - invalid",
|
||||||
|
environmentVariables: map[string]string{"numberOfCats": "please"},
|
||||||
|
expression: `env(numberOfCats) | select(tag == "!!int") or error("numberOfCats is not a number :(")`,
|
||||||
|
expectedError: "numberOfCats is not a number :(",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Validate the environment variable is a number - valid",
|
||||||
|
subdescription: "`with` can be a convenient way of encapsulating validation.",
|
||||||
|
environmentVariables: map[string]string{"numberOfCats": "3"},
|
||||||
|
document: "name: Bob\nfavouriteAnimal: cat\n",
|
||||||
|
expression: validationExpression,
|
||||||
|
expected: []string{
|
||||||
|
"D0, P[], (doc)::name: Bob\nfavouriteAnimal: cat\nnumPets: 3\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorOperatorScenarios(t *testing.T) {
|
||||||
|
for _, tt := range errorOperatorScenarios {
|
||||||
|
testScenario(t, &tt)
|
||||||
|
}
|
||||||
|
documentOperatorScenarios(t, "error", errorOperatorScenarios)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user