Skip to content

Commit

Permalink
feat: add unique validator for table variables
Browse files Browse the repository at this point in the history
Added new validator which ensures values are unique within given column.

Fixes #71
  • Loading branch information
vesse committed May 13, 2024
1 parent cad4b87 commit 50235f6
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 18 deletions.
8 changes: 5 additions & 3 deletions examples/variable-types/recipe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ vars:

- name: TABLE_VAR_WITH_VALIDATOR
description: |
Regular expression validators can be set for a table variable by defining `validators` and `column` property
columns: [NOT_EMPTY_COL, CAN_BE_EMPTY_COL]
Validators can be set for a table variable by defining `validators` and `column` property
columns: [NOT_EMPTY_UNIQUE_COL, CAN_BE_EMPTY_COL]
validators:
- pattern: ".+"
column: NOT_EMPTY_COL
column: NOT_EMPTY_UNIQUE_COL
help: "If the cell is empty, this help message will be shown"
- unique: true
column: NOT_EMPTY_UNIQUE_COL
48 changes: 39 additions & 9 deletions pkg/recipe/variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"regexp"
"slices"
"strings"

"github.com/expr-lang/expr"
Expand Down Expand Up @@ -179,19 +180,48 @@ func (val VariableValues) Validate() error {
return nil
}

func (r *VariableValidator) CreateTableValidatorFunc() func(cols []string, rows [][]string, input string) error {
if r.Unique {
return func(cols []string, rows [][]string, input string) error {
colIndex := slices.Index(cols, r.Column)
colValues := make([]string, len(rows))
for i, row := range rows {
colValues[i] = row[colIndex]
}
slices.Sort(colValues)

if uniqValues := len(slices.Compact(colValues)); uniqValues != len(colValues) {
if r.Help != "" {
return errors.New(r.Help)
} else {
return errors.New("value not unique within column")
}
}

return nil
}
}

panic(fmt.Errorf("unsupported table validator on column %q", r.Column))
}

func (r *VariableValidator) CreateValidatorFunc() func(input string) error {
reg := regexp.MustCompile(r.Pattern)

return func(input string) error {
if match := reg.MatchString(input); !match {
if r.Help != "" {
return errors.New(r.Help)
} else {
return errors.New("the input did not match the regexp pattern")
if r.Pattern != "" {
reg := regexp.MustCompile(r.Pattern)

return func(input string) error {
if match := reg.MatchString(input); !match {
if r.Help != "" {
return errors.New(r.Help)
} else {
return errors.New("the input did not match the regexp pattern")
}
}
return nil
}
return nil
}

panic(fmt.Errorf("unsupported validator on column %q", r.Column))
}

func (t *TableValue) FromCSV(columns []string, input string, delimiter rune) error {
Expand Down
53 changes: 53 additions & 0 deletions pkg/recipe/variable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,56 @@ func TestVariableRegExpValidation(t *testing.T) {
t.Error("Incorrectly invalidated valid string")
}
}

func TestUniqueColumnValidation(t *testing.T) {
variable := &Variable{
Name: "foo",
Description: "foo description",
Validators: []VariableValidator{
{
Unique: true,
Column: "COL_1",
},
},
}

validatorFunc := variable.Validators[0].CreateTableValidatorFunc()

cols := []string{"COL_1", "COL_2"}

err := validatorFunc(
cols,
[][]string{
{"0_0", "0_1"},
{"1_0", "1_1"},
{"2_0", "2_1"},
},
"")
if err != nil {
t.Error("Incorrectly invalidated valid data")
}

err = validatorFunc(
cols,
[][]string{
{"0_0", "0_1"},
{"0_0", "1_1"},
{"2_0", "2_1"},
},
"")
if err == nil {
t.Error("Incorrectly validated invalid data")
}

err = validatorFunc(
cols,
[][]string{
{"0_0", "0_1"},
{"1_0", "0_1"},
{"2_0", "0_1"},
},
"")
if err != nil {
t.Error("Incorrectly invalidated valid data")
}
}
13 changes: 11 additions & 2 deletions pkg/ui/editable/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type Cell struct {
type Column struct {
Title string
Width int
Validators []func(string) error
Validators []func([]string, [][]string, string) error
}

type KeyMap struct {
Expand Down Expand Up @@ -375,6 +375,15 @@ func (m *Model) Move(y, x int) tea.Cmd {
return m.rows[m.cursorY][m.cursorX].input.Focus()
}

func (m Model) Titles() []string {
titles := make([]string, len(m.cols))
for i, col := range m.cols {
titles[i] = col.Title
}

return titles
}

func (m Model) Values() [][]string {
// If the table has only empty cells, return an empty slice
if m.isEmpty() {
Expand Down Expand Up @@ -428,7 +437,7 @@ func (m *Model) validateCell(y, x int) {

errs := make([]error, 0, len(m.cols[x].Validators))
for i := range m.cols[x].Validators {
err := m.cols[x].Validators[i](cell.input.Value())
err := m.cols[x].Validators[i](m.Titles(), m.Values(), cell.input.Value())
if err != nil {
errs = append(errs, err)
}
Expand Down
18 changes: 14 additions & 4 deletions pkg/ui/survey/prompt/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,27 @@ var _ Model = TableModel{}
func NewTableModel(v recipe.Variable, styles style.Styles) TableModel {
cols := make([]editable.Column, len(v.Columns))

validators := make(map[string][]func(string) error)
validators := make(map[string][]func([]string, [][]string, string) error)
for i, validator := range v.Validators {
if validator.Column != "" {
if validators[validator.Column] == nil {
validators[validator.Column] = make([]func(string) error, 0)
validators[validator.Column] = make([]func([]string, [][]string, string) error, 0)
}

validators[validator.Column] = append(validators[validator.Column], v.Validators[i].CreateValidatorFunc())
if validator.Pattern != "" {
validators[validator.Column] = append(validators[validator.Column],
func() func(cols []string, rows [][]string, input string) error {
regexValidator := v.Validators[i].CreateValidatorFunc()

return func(cols []string, rows [][]string, input string) error {
return regexValidator(input)
}
}())
} else {
validators[validator.Column] = append(validators[validator.Column], validator.CreateTableValidatorFunc())
}
}
}

for i, c := range v.Columns {
cols[i] = editable.Column{
Title: c,
Expand Down

0 comments on commit 50235f6

Please sign in to comment.