mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-05-02 22:34:56 -04:00
validation: add generic validation framework (#2480)
* [wip] validation framework Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [wip] wip Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * working for shallow structs!!! Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix needle pointer deref Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add comment Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix nested structs Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix nested struct pointers Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add tests Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix slices / arrays Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix struct parsing Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * extend tests Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * expose API Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * extend in-package documentation Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * linter fixes Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix naming Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add missing license headers Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * align with review Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> --------- Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com>
This commit is contained in:
parent
2f745a2edb
commit
a104936bc6
6 changed files with 1184 additions and 0 deletions
476
internal/validation/errors_test.go
Normal file
476
internal/validation/errors_test.go
Normal file
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Tests for primitive / shallow fields
|
||||
|
||||
func TestNewValidationErrorSingleField(t *testing.T) {
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.OtherField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.otherField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorSingleFieldPtr(t *testing.T) {
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
PointerField: new(int),
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.PointerField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.pointerField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorSingleFieldDoublePtr(t *testing.T) {
|
||||
intp := new(int)
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
DoublePointerField: &intp,
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.DoublePointerField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.doublePointerField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorSingleFieldInexistent(t *testing.T) {
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
PointerField: new(int),
|
||||
}
|
||||
|
||||
inexistentField := 123
|
||||
|
||||
doc, field := references(t, st, &inexistentField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "cannot find path to field: cannot traverse anymore")
|
||||
}
|
||||
|
||||
// Tests for nested structs
|
||||
|
||||
func TestNewValidationErrorNestedField(t *testing.T) {
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
NestedField: nestederrorTestDoc{
|
||||
ExportedField: "nested",
|
||||
OtherField: 123,
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.NestedField.OtherField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.nestedField.otherField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorPointerInNestedField(t *testing.T) {
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
NestedField: nestederrorTestDoc{
|
||||
ExportedField: "nested",
|
||||
OtherField: 123,
|
||||
PointerField: new(int),
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.NestedField.PointerField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.nestedField.pointerField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorNestedFieldPtr(t *testing.T) {
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
NestedField: nestederrorTestDoc{
|
||||
ExportedField: "nested",
|
||||
OtherField: 123,
|
||||
},
|
||||
NestedPointerField: &nestederrorTestDoc{
|
||||
ExportedField: "nested",
|
||||
OtherField: 123,
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.NestedPointerField.OtherField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.nestedPointerField.otherField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorNestedNestedField(t *testing.T) {
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
NestedField: nestederrorTestDoc{
|
||||
ExportedField: "nested",
|
||||
OtherField: 123,
|
||||
NestedField: nestedNestederrorTestDoc{
|
||||
ExportedField: "nested",
|
||||
OtherField: 123,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.NestedField.NestedField.OtherField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.nestedField.nestedField.otherField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorNestedNestedFieldPtr(t *testing.T) {
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
NestedField: nestederrorTestDoc{
|
||||
ExportedField: "nested",
|
||||
OtherField: 123,
|
||||
NestedPointerField: &nestedNestederrorTestDoc{
|
||||
ExportedField: "nested",
|
||||
OtherField: 123,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.NestedField.NestedPointerField.OtherField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.nestedField.nestedPointerField.otherField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorNestedPtrNestedFieldPtr(t *testing.T) {
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
NestedPointerField: &nestederrorTestDoc{
|
||||
ExportedField: "nested",
|
||||
OtherField: 123,
|
||||
NestedPointerField: &nestedNestederrorTestDoc{
|
||||
ExportedField: "nested",
|
||||
OtherField: 123,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.NestedPointerField.NestedPointerField.OtherField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.nestedPointerField.nestedPointerField.otherField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
// Tests for slices / arrays
|
||||
|
||||
func TestNewValidationErrorPrimitiveSlice(t *testing.T) {
|
||||
st := &sliceErrorTestDoc{
|
||||
PrimitiveSlice: []string{"abc", "def"},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.PrimitiveSlice[1], "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.primitiveSlice[1]: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorPrimitiveArray(t *testing.T) {
|
||||
st := &sliceErrorTestDoc{
|
||||
PrimitiveArray: [3]int{1, 2, 3},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.PrimitiveArray[1], "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.primitiveArray[1]: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorStructSlice(t *testing.T) {
|
||||
st := &sliceErrorTestDoc{
|
||||
StructSlice: []errorTestDoc{
|
||||
{
|
||||
ExportedField: "abc",
|
||||
OtherField: 123,
|
||||
},
|
||||
{
|
||||
ExportedField: "def",
|
||||
OtherField: 456,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.StructSlice[1].OtherField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.structSlice[1].otherField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorStructArray(t *testing.T) {
|
||||
st := &sliceErrorTestDoc{
|
||||
StructArray: [3]errorTestDoc{
|
||||
{
|
||||
ExportedField: "abc",
|
||||
OtherField: 123,
|
||||
},
|
||||
{
|
||||
ExportedField: "def",
|
||||
OtherField: 456,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.StructArray[1].OtherField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.structArray[1].otherField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorStructPointerSlice(t *testing.T) {
|
||||
st := &sliceErrorTestDoc{
|
||||
StructPointerSlice: []*errorTestDoc{
|
||||
{
|
||||
ExportedField: "abc",
|
||||
OtherField: 123,
|
||||
},
|
||||
{
|
||||
ExportedField: "def",
|
||||
OtherField: 456,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.StructPointerSlice[1].OtherField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.structPointerSlice[1].otherField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorStructPointerArray(t *testing.T) {
|
||||
st := &sliceErrorTestDoc{
|
||||
StructPointerArray: [3]*errorTestDoc{
|
||||
{
|
||||
ExportedField: "abc",
|
||||
OtherField: 123,
|
||||
},
|
||||
{
|
||||
ExportedField: "def",
|
||||
OtherField: 456,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.StructPointerArray[1].OtherField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.structPointerArray[1].otherField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorPrimitiveSliceSlice(t *testing.T) {
|
||||
st := &sliceErrorTestDoc{
|
||||
PrimitiveSliceSlice: [][]string{
|
||||
{"abc", "def"},
|
||||
{"ghi", "jkl"},
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.PrimitiveSliceSlice[1][1], "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating sliceErrorTestDoc.primitiveSliceSlice[1][1]: %s", assert.AnError))
|
||||
}
|
||||
|
||||
// Tests for maps
|
||||
|
||||
func TestNewValidationErrorPrimitiveMap(t *testing.T) {
|
||||
st := &mapErrorTestDoc{
|
||||
PrimitiveMap: map[string]string{
|
||||
"abc": "def",
|
||||
"ghi": "jkl",
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.PrimitiveMap, "ghi")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating mapErrorTestDoc.primitiveMap[\"ghi\"]: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorStructPointerMap(t *testing.T) {
|
||||
st := &mapErrorTestDoc{
|
||||
StructPointerMap: map[string]*errorTestDoc{
|
||||
"abc": {
|
||||
ExportedField: "abc",
|
||||
OtherField: 123,
|
||||
},
|
||||
"ghi": {
|
||||
ExportedField: "ghi",
|
||||
OtherField: 456,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.StructPointerMap["ghi"].OtherField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating mapErrorTestDoc.structPointerMap[\"ghi\"].otherField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorNestedPrimitiveMap(t *testing.T) {
|
||||
st := &mapErrorTestDoc{
|
||||
NestedPointerMap: map[string]*map[string]string{
|
||||
"abc": {
|
||||
"def": "ghi",
|
||||
},
|
||||
"jkl": {
|
||||
"mno": "pqr",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
doc, field := references(t, st, st.NestedPointerMap["jkl"], "mno")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
t.Log(err)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating mapErrorTestDoc.nestedPointerMap[\"jkl\"][\"mno\"]: %s", assert.AnError))
|
||||
}
|
||||
|
||||
// Special cases
|
||||
|
||||
func TestNewValidationErrorTopLevelIsNeedle(t *testing.T) {
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
}
|
||||
|
||||
doc, field := references(t, st, st, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorUntaggedField(t *testing.T) {
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
NoTagField: 123,
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.NoTagField, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.NoTagField: %s", assert.AnError))
|
||||
}
|
||||
|
||||
func TestNewValidationErrorOnlyYamlTaggedField(t *testing.T) {
|
||||
st := &errorTestDoc{
|
||||
ExportedField: "abc",
|
||||
OtherField: 42,
|
||||
NoTagField: 123,
|
||||
OnlyYamlKey: "abc",
|
||||
}
|
||||
|
||||
doc, field := references(t, st, &st.OnlyYamlKey, "")
|
||||
err := newError(doc, field, assert.AnError)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), fmt.Sprintf("validating errorTestDoc.onlyYamlKey: %s", assert.AnError))
|
||||
}
|
||||
|
||||
type errorTestDoc struct {
|
||||
ExportedField string `json:"exportedField" yaml:"exportedField"`
|
||||
OtherField int `json:"otherField" yaml:"otherField"`
|
||||
PointerField *int `json:"pointerField" yaml:"pointerField"`
|
||||
DoublePointerField **int `json:"doublePointerField" yaml:"doublePointerField"`
|
||||
NestedField nestederrorTestDoc `json:"nestedField" yaml:"nestedField"`
|
||||
NestedPointerField *nestederrorTestDoc `json:"nestedPointerField" yaml:"nestedPointerField"`
|
||||
NoTagField int
|
||||
OnlyYamlKey string `yaml:"onlyYamlKey"`
|
||||
}
|
||||
|
||||
type nestederrorTestDoc struct {
|
||||
ExportedField string `json:"exportedField" yaml:"exportedField"`
|
||||
OtherField int `json:"otherField" yaml:"otherField"`
|
||||
PointerField *int `json:"pointerField" yaml:"pointerField"`
|
||||
NestedField nestedNestederrorTestDoc `json:"nestedField" yaml:"nestedField"`
|
||||
NestedPointerField *nestedNestederrorTestDoc `json:"nestedPointerField" yaml:"nestedPointerField"`
|
||||
}
|
||||
|
||||
type nestedNestederrorTestDoc struct {
|
||||
ExportedField string `json:"exportedField" yaml:"exportedField"`
|
||||
OtherField int `json:"otherField" yaml:"otherField"`
|
||||
PointerField *int `json:"pointerField" yaml:"pointerField"`
|
||||
}
|
||||
|
||||
type sliceErrorTestDoc struct {
|
||||
PrimitiveSlice []string `json:"primitiveSlice" yaml:"primitiveSlice"`
|
||||
PrimitiveArray [3]int `json:"primitiveArray" yaml:"primitiveArray"`
|
||||
StructSlice []errorTestDoc `json:"structSlice" yaml:"structSlice"`
|
||||
StructArray [3]errorTestDoc `json:"structArray" yaml:"structArray"`
|
||||
StructPointerSlice []*errorTestDoc `json:"structPointerSlice" yaml:"structPointerSlice"`
|
||||
StructPointerArray [3]*errorTestDoc `json:"structPointerArray" yaml:"structPointerArray"`
|
||||
PrimitiveSliceSlice [][]string `json:"primitiveSliceSlice" yaml:"primitiveSliceSlice"`
|
||||
}
|
||||
|
||||
type mapErrorTestDoc struct {
|
||||
PrimitiveMap map[string]string `json:"primitiveMap" yaml:"primitiveMap"`
|
||||
StructPointerMap map[string]*errorTestDoc `json:"structPointerMap" yaml:"structPointerMap"`
|
||||
NestedPointerMap map[string]*map[string]string `json:"nestedPointerMap" yaml:"nestedPointerMap"`
|
||||
}
|
||||
|
||||
// references returns referenceableValues for the given doc and field for testing purposes.
|
||||
func references(t *testing.T, doc, field any, mapKey string) (haystack, needle referenceableValue) {
|
||||
t.Helper()
|
||||
derefedField := pointerDeref(reflect.ValueOf(field))
|
||||
fieldRef := referenceableValue{
|
||||
value: derefedField,
|
||||
addr: derefedField.UnsafeAddr(),
|
||||
_type: derefedField.Type(),
|
||||
mapKey: mapKey,
|
||||
}
|
||||
derefedDoc := pointerDeref(reflect.ValueOf(doc))
|
||||
docRef := referenceableValue{
|
||||
value: derefedDoc,
|
||||
addr: derefedDoc.UnsafeAddr(),
|
||||
_type: derefedDoc.Type(),
|
||||
}
|
||||
return docRef, fieldRef
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue