blob: 9ca82ac2ed282e96d7765d7836081db2a45d2fed [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package validators
import (
"fmt"
"strings"
)
type ValidationError struct {
Violations []Violation `json:"violations"`
}
type Violation struct {
Field string `json:"field"`
Message string `json:"message"`
}
// OK returns and empty validation error (i.e. success).
func OK() ValidationError {
return ValidationError{}
}
func (v *ValidationError) Error() string {
msg := ""
for _, violation := range v.Violations {
if msg != "" {
msg = fmt.Sprintf("%s; %s: %s", msg, violation.Field, violation.Message)
} else {
msg += fmt.Sprintf("%s: %s", violation.Field, violation.Message)
}
}
return msg
}
func (v *ValidationError) HasViolations() bool {
return len(v.Violations) > 0
}
func (v *ValidationError) OrNil() error {
if v.HasViolations() {
return v
}
return nil
}
func (v *ValidationError) AddViolationAt(path PathBuilder, message string) {
v.AddViolation(path.String(), message)
}
func (v *ValidationError) AddViolation(field string, message string) {
violation := Violation{
Field: field,
Message: message,
}
v.Violations = append(v.Violations, violation)
}
func (v *ValidationError) AddErrorAt(path PathBuilder, validationErr ValidationError) {
for _, violation := range validationErr.Violations {
field := Root()
if violation.Field != "" {
field = RootedAt(violation.Field)
}
newViolation := Violation{
Field: path.concat(field).String(),
Message: violation.Message,
}
v.Violations = append(v.Violations, newViolation)
}
}
func (v *ValidationError) Add(err ValidationError) {
v.AddErrorAt(Root(), err)
}
func (v *ValidationError) AddError(rootField string, validationErr ValidationError) {
root := Root()
if rootField != "" {
root = RootedAt(rootField)
}
v.AddErrorAt(root, validationErr)
}
// Transform returns a new ValidationError with every violation
// transformed by a given transformFunc.
func (v *ValidationError) Transform(transformFunc func(Violation) Violation) *ValidationError {
if v == nil {
return nil
}
if transformFunc == nil || len(v.Violations) == 0 {
rv := *v
return &rv
}
result := ValidationError{
Violations: make([]Violation, len(v.Violations)),
}
for i := range v.Violations {
result.Violations[i] = transformFunc(v.Violations[i])
}
return &result
}
func MakeUnimplementedFieldErr(path PathBuilder) ValidationError {
var err ValidationError
err.AddViolationAt(path, "field is not implemented")
return err
}
func MakeRequiredFieldErr(path PathBuilder) ValidationError {
var err ValidationError
err.AddViolationAt(path, "cannot be empty")
return err
}
func MakeOneOfErr(fieldA, fieldB, msg string, oneOf []string) ValidationError {
var err ValidationError
var quoted []string
for _, value := range oneOf {
quoted = append(quoted, fmt.Sprintf("%q", value))
}
message := fmt.Sprintf(
"%q %s one of [%s]",
fieldA,
msg,
strings.Join(quoted, ", "),
)
if fieldB != "" {
message = fmt.Sprintf(
"%q %s when %q is one of [%s]",
fieldA,
msg,
fieldB,
strings.Join(quoted, ", "),
)
}
err.AddViolationAt(Root(), message)
return err
}
func MakeFieldMustBeOneOfErr(field string, allowed ...string) ValidationError {
return MakeOneOfErr(field, "", "must be", allowed)
}
func IsValidationError(err error) bool {
_, ok := err.(*ValidationError)
return ok
}
type PathBuilder []string
func RootedAt(name string) PathBuilder {
return PathBuilder{name}
}
func Root() PathBuilder {
return PathBuilder{}
}
func (p PathBuilder) Field(name string) PathBuilder {
element := name
if len(p) > 0 {
element = fmt.Sprintf(".%s", element)
}
return append(p, element)
}
func (p PathBuilder) Index(index int) PathBuilder {
return append(p, fmt.Sprintf("[%d]", index))
}
func (p PathBuilder) Key(key string) PathBuilder {
return append(p, fmt.Sprintf("[%q]", key))
}
func (p PathBuilder) String() string {
return strings.Join(p, "")
}
func (p PathBuilder) concat(other PathBuilder) PathBuilder {
if len(other) == 0 {
return p
}
if len(p) == 0 {
return other
}
firstOther := other[0]
if !strings.HasPrefix(firstOther, "[") {
firstOther = "." + firstOther
}
return append(append(p, firstOther), other[1:]...)
}