parent
7d3c8af2c9
commit
0c04b0e3f7
|
@ -34,6 +34,7 @@ import (
|
|||
semconv "go.opentelemetry.io/otel/semconv/v1.25.0"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
|
||||
"github.com/letsencrypt/boulder/config"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/strictyaml"
|
||||
|
@ -455,6 +456,9 @@ func ValidateJSONConfig(cv *ConfigValidator, in io.Reader) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Register custom types for use with existing validation tags.
|
||||
validate.RegisterCustomTypeFunc(config.DurationCustomTypeFunc, config.Duration{})
|
||||
|
||||
err := decodeJSONStrict(in, cv.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -497,6 +501,9 @@ func ValidateYAMLConfig(cv *ConfigValidator, in io.Reader) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Register custom types for use with existing validation tags.
|
||||
validate.RegisterCustomTypeFunc(config.DurationCustomTypeFunc, config.Duration{})
|
||||
|
||||
inBytes, err := io.ReadAll(in)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -11,10 +11,12 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/letsencrypt/boulder/config"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -196,9 +198,11 @@ func loadConfigFile(t *testing.T, path string) *os.File {
|
|||
|
||||
func TestFailedConfigValidation(t *testing.T) {
|
||||
type FooConfig struct {
|
||||
VitalValue string `yaml:"vitalValue" validate:"required"`
|
||||
VoluntarilyVoid string `yaml:"voluntarilyVoid"`
|
||||
VisciouslyVetted string `yaml:"visciouslyVetted" validate:"omitempty,endswith=baz"`
|
||||
VitalValue string `yaml:"vitalValue" validate:"required"`
|
||||
VoluntarilyVoid string `yaml:"voluntarilyVoid"`
|
||||
VisciouslyVetted string `yaml:"visciouslyVetted" validate:"omitempty,endswith=baz"`
|
||||
VolatileVagary config.Duration `yaml:"volatileVagary" validate:"required,lte=120s"`
|
||||
VernalVeil config.Duration `yaml:"vernalVeil" validate:"required"`
|
||||
}
|
||||
|
||||
// Violates 'endswith' tag JSON.
|
||||
|
@ -228,6 +232,34 @@ func TestFailedConfigValidation(t *testing.T) {
|
|||
err = ValidateYAMLConfig(&ConfigValidator{&FooConfig{}, nil}, cf)
|
||||
test.AssertError(t, err, "Expected validation error")
|
||||
test.AssertContains(t, err.Error(), "'required'")
|
||||
|
||||
// Violates 'lte' tag JSON for config.Duration type.
|
||||
cf = loadConfigFile(t, "testdata/3_configDuration_too_darn_big.json")
|
||||
defer cf.Close()
|
||||
err = ValidateJSONConfig(&ConfigValidator{&FooConfig{}, nil}, cf)
|
||||
test.AssertError(t, err, "Expected validation error")
|
||||
test.AssertContains(t, err.Error(), "'lte'")
|
||||
|
||||
// Violates 'lte' tag JSON for config.Duration type.
|
||||
cf = loadConfigFile(t, "testdata/3_configDuration_too_darn_big.json")
|
||||
defer cf.Close()
|
||||
err = ValidateJSONConfig(&ConfigValidator{&FooConfig{}, nil}, cf)
|
||||
test.AssertError(t, err, "Expected validation error")
|
||||
test.AssertContains(t, err.Error(), "'lte'")
|
||||
|
||||
// Incorrect value for the config.Duration type.
|
||||
cf = loadConfigFile(t, "testdata/4_incorrect_data_for_type.json")
|
||||
defer cf.Close()
|
||||
err = ValidateJSONConfig(&ConfigValidator{&FooConfig{}, nil}, cf)
|
||||
test.AssertError(t, err, "Expected error")
|
||||
test.AssertContains(t, err.Error(), "missing unit in duration")
|
||||
|
||||
// Incorrect value for the config.Duration type.
|
||||
cf = loadConfigFile(t, "testdata/4_incorrect_data_for_type.yaml")
|
||||
defer cf.Close()
|
||||
err = ValidateYAMLConfig(&ConfigValidator{&FooConfig{}, nil}, cf)
|
||||
test.AssertError(t, err, "Expected error")
|
||||
test.AssertContains(t, err.Error(), "missing unit in duration")
|
||||
}
|
||||
|
||||
func TestFailExit(t *testing.T) {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"vitalValue": "Gotcha",
|
||||
"voluntarilyVoid": "Not used",
|
||||
"visciouslyVetted": "Whateverbaz",
|
||||
"volatileVagary": "121s"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"vitalValue": "Gotcha",
|
||||
"voluntarilyVoid": "Not used",
|
||||
"visciouslyVetted": "Whateverbaz",
|
||||
"volatileVagary": "120s",
|
||||
"vernalVeil": "60"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
vitalValue: "Gotcha"
|
||||
voluntarilyVoid: "Not used"
|
||||
visciouslyVetted: "Whateverbaz"
|
||||
volatileVagary: "120s"
|
||||
vernalVeil: "60"
|
|
@ -3,15 +3,27 @@ package config
|
|||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Duration is just an alias for time.Duration that allows
|
||||
// serialization to YAML as well as JSON.
|
||||
// Duration is custom type embedding a time.Duration which allows defining
|
||||
// methods such as serialization to YAML or JSON.
|
||||
type Duration struct {
|
||||
time.Duration `validate:"required"`
|
||||
}
|
||||
|
||||
// DurationCustomTypeFunc enables registration of our custom config.Duration
|
||||
// type as a time.Duration and performing validation on the configured value
|
||||
// using the standard suite of validation functions.
|
||||
func DurationCustomTypeFunc(field reflect.Value) interface{} {
|
||||
if c, ok := field.Interface().(Duration); ok {
|
||||
return c.Duration
|
||||
}
|
||||
|
||||
return reflect.Invalid
|
||||
}
|
||||
|
||||
// ErrDurationMustBeString is returned when a non-string value is
|
||||
// presented to be deserialized as a ConfigDuration
|
||||
var ErrDurationMustBeString = errors.New("cannot JSON unmarshal something other than a string into a ConfigDuration")
|
||||
|
|
Loading…
Reference in New Issue