llresult/results.go (94 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. 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 llresult
import (
"fmt"
"github.com/elastic/go-lookslike/llpath"
)
// Results the results of executing a schema.
// They are a flattened map (using dotted paths) of all the values ValueResult representing the results
// of the IsDefs.
type Results struct {
Fields map[string][]ValueResult
Valid bool
}
// ValueResult represents the result of checking a leaf value.
type ValueResult struct {
Valid bool
Message string // Reason this is invalid
}
// NewResults creates a new Results object.
func NewResults() *Results {
return &Results{
Fields: make(map[string][]ValueResult),
Valid: true,
}
}
// SimpleResult provides a convenient and simple method for creating a *Results object for a single validation.
// It's a very common way for validators to return a *Results object, and is generally simpler than
// using SingleResult.
func SimpleResult(path llpath.Path, valid bool, msg string, args ...interface{}) *Results {
vr := ValueResult{valid, fmt.Sprintf(msg, args...)}
return SingleResult(path, vr)
}
// SingleResult returns a *Results object with a single validated value at the given Path
// using the providedValueResult as its sole validation.
func SingleResult(path llpath.Path, result ValueResult) *Results {
r := NewResults()
r.Record(path, result)
return r
}
// Merge combines multiple *Results sets together.
func (r *Results) Merge(other *Results) {
for otherPath, valueResults := range other.Fields {
for _, valueResult := range valueResults {
r.Record(llpath.MustParsePath(otherPath), valueResult)
}
}
}
// MergeUnderPrefix merges the given results at the path specified by the given prefix.
func (r *Results) MergeUnderPrefix(prefix llpath.Path, other *Results) {
if len(prefix) == 0 {
// If the prefix is empty, just use standard Merge
// No need to add the dots
r.Merge(other)
return
}
for otherPath, valueResults := range other.Fields {
for _, valueResult := range valueResults {
parsed := llpath.MustParsePath(otherPath)
r.Record(prefix.Concat(parsed), valueResult)
}
}
}
// Record records a single path result to this instance.
func (r *Results) Record(p llpath.Path, result ValueResult) {
if r.Fields[p.String()] == nil {
r.Fields[p.String()] = []ValueResult{result}
} else {
r.Fields[p.String()] = append(r.Fields[p.String()], result)
}
if !result.Valid {
r.Valid = false
}
}
// EachResult executes the given callback once per Value result.
// The provided callback can return true to keep iterating, or false
// to stop.
func (r Results) EachResult(f func(llpath.Path, ValueResult) bool) {
for p, pathResults := range r.Fields {
for _, result := range pathResults {
// We can ignore path parse errors here, those are from scalars and other
// types that have an invalid string path
// TODO: Find a cleaner way to do this
parsed, _ := llpath.ParsePath(p)
if !f(parsed, result) {
return
}
}
}
}
// DetailedErrors returns a new Results object consisting only of error data.
func (r *Results) DetailedErrors() *Results {
errors := NewResults()
r.EachResult(func(p llpath.Path, vr ValueResult) bool {
if !vr.Valid {
errors.Record(p, vr)
}
return true
})
return errors
}
// ValueResultError is used to represent an error validating an individual value.
type ValueResultError struct {
path llpath.Path
valueResult ValueResult
}
// Error returns the error that occurred during validation with its context included.
func (vre ValueResultError) Error() string {
return fmt.Sprintf("@Path '%s': %s", vre.path, vre.valueResult.Message)
}
// Errors returns a list of error objects, one per failed value validation.
func (r Results) Errors() []error {
errors := make([]error, 0)
r.EachResult(func(path llpath.Path, vr ValueResult) bool {
if !vr.Valid {
errors = append(errors, ValueResultError{path, vr})
}
return true
})
return errors
}