1
0
Fork 0
mirror of https://github.com/Luzifer/go_helpers.git synced 2024-10-18 06:14:21 +00:00
go_helpers/fieldcollection/schema.go
Knut Ahlers 0c39806be9
Add float64 field type support
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-05-04 23:04:33 +02:00

187 lines
4.8 KiB
Go

package fieldcollection
import (
"fmt"
"sort"
"strings"
"github.com/Luzifer/go_helpers/v2/str"
)
const (
knownFields = "knownFields"
)
type (
// SchemaField defines how a field is expected to be
SchemaField struct {
// Name of the field to validate
Name string
// If set to true the field must i.e. not be "" for a string field
NonEmpty bool
// The expected type of the field
Type SchemaFieldType
}
// SchemaFieldType is a collection of known field types for which
// can be checked
SchemaFieldType uint64
// ValidateOpt is a validation function to be executed during the
// validation call
ValidateOpt func(f, validateStore *FieldCollection) error
)
// Collection of known field types for which can be checked
const (
SchemaFieldTypeAny SchemaFieldType = iota
SchemaFieldTypeBool
SchemaFieldTypeDuration
SchemaFieldTypeFloat64
SchemaFieldTypeInt64
SchemaFieldTypeString
SchemaFieldTypeStringSlice
)
// CanHaveField validates the type of the field if it exists and puts
// the field to the allow-list for MustHaveNoUnknowFields
func CanHaveField(field SchemaField) ValidateOpt {
return func(f, validateStore *FieldCollection) error {
validateStore.Set(knownFields, append(validateStore.MustStringSlice(knownFields, nil), field.Name))
if !f.HasAll(field.Name) {
// It is allowed to not exist, and if it does not we don't need
// to type-check it
return nil
}
return validateFieldType(f, field)
}
}
// MustHaveField validates the type of the field and puts the field to
// the allow-list for MustHaveNoUnknowFields
func MustHaveField(field SchemaField) ValidateOpt {
return func(f, validateStore *FieldCollection) error {
validateStore.Set(knownFields, append(validateStore.MustStringSlice(knownFields, nil), field.Name))
if !f.HasAll(field.Name) {
// It must exist and does not
return fmt.Errorf("field %s does not exist", field.Name)
}
return validateFieldType(f, field)
}
}
// MustHaveNoUnknowFields validates no fields are present which are
// not previously allow-listed through CanHaveField or MustHaveField
// and therefore should be put as the last ValidateOpt
func MustHaveNoUnknowFields(f *FieldCollection, validateStore *FieldCollection) error {
var unexpected []string
for _, k := range f.Keys() {
if !str.StringInSlice(k, validateStore.MustStringSlice(knownFields, nil)) {
unexpected = append(unexpected, k)
}
}
sort.Strings(unexpected)
if len(unexpected) > 0 {
return fmt.Errorf("found unexpected fields: %s", strings.Join(unexpected, ", "))
}
return nil
}
// ValidateSchema can be used to validate the contents of the
// FieldCollection by passing in field definitions which may be there
// or must be there and to check whether there are no surplus fields
func (f *FieldCollection) ValidateSchema(opts ...ValidateOpt) error {
validateStore := NewFieldCollection()
validateStore.Set(knownFields, []string{})
for _, opt := range opts {
if err := opt(f, validateStore); err != nil {
return err
}
}
return nil
}
//nolint:gocognit,gocyclo // These are quite simple checks
func validateFieldType(f *FieldCollection, field SchemaField) (err error) {
switch field.Type {
case SchemaFieldTypeAny:
v, err := f.Get(field.Name)
if err != nil {
return fmt.Errorf("getting field %s: %w", field.Name, err)
}
if field.NonEmpty && v == nil {
return fmt.Errorf("field %s is empty", field.Name)
}
case SchemaFieldTypeBool:
if !f.CanBool(field.Name) {
return fmt.Errorf("field %s is not of type bool", field.Name)
}
case SchemaFieldTypeDuration:
v, err := f.Duration(field.Name)
if err != nil {
return fmt.Errorf("field %s is not of type time.Duration: %w", field.Name, err)
}
if field.NonEmpty && v == 0 {
return fmt.Errorf("field %s is empty", field.Name)
}
case SchemaFieldTypeFloat64:
v, err := f.Float64(field.Name)
if err != nil {
return fmt.Errorf("field %s is not of type float64: %w", field.Name, err)
}
if field.NonEmpty && v == 0 {
return fmt.Errorf("field %s is empty", field.Name)
}
case SchemaFieldTypeInt64:
v, err := f.Int64(field.Name)
if err != nil {
return fmt.Errorf("field %s is not of type int64: %w", field.Name, err)
}
if field.NonEmpty && v == 0 {
return fmt.Errorf("field %s is empty", field.Name)
}
case SchemaFieldTypeString:
v, err := f.String(field.Name)
if err != nil {
return fmt.Errorf("field %s is not of type string: %w", field.Name, err)
}
if field.NonEmpty && v == "" {
return fmt.Errorf("field %s is empty", field.Name)
}
case SchemaFieldTypeStringSlice:
v, err := f.StringSlice(field.Name)
if err != nil {
return fmt.Errorf("field %s is not of type []string: %w", field.Name, err)
}
if field.NonEmpty && len(v) == 0 {
return fmt.Errorf("field %s is empty", field.Name)
}
default:
return fmt.Errorf("unknown field type specified")
}
return nil
}