blob: d6cbd4beae19c1399cf481aeb2278dfdbdcdc253 [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed 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 compare
import "fmt"
// Path represents a path from the root of an object hierarchy
type Path []Fragment
// Fragment is an entry in a Path
type Fragment struct {
// Operation holds the operation occurring at this level in the path.
Operation interface{}
// Reference holds the entry in the reference hierarchy to apply the operation to
Reference interface{}
// Value holds the equivalent entry in the value hierarchy to apply the the operation to
Value interface{}
}
// MemberOp is the fragment operation type for member comparisons.
type MemberOp string
// IndexOp is the fragment operation type for array / slice index comparisons.
type IndexOp int
// EntryOp is the fragment operation type for map entry comparisons.
type EntryOp struct {
Key interface{} // The map key
}
// LengthOp is the fragment operation type for array / slice length comparisons.
type LengthOp string
// TypeOp is the fragment operation type for type comparisons.
type TypeOp string
// NilOp is the fragment operation type for nil-equality comparisons.
type NilOp string
// MissingOp is the fragment operation type for absent entries in arrays,
// slices and maps.
type MissingOp string
const (
Length = LengthOp("·length")
Type = TypeOp("·type")
Nil = NilOp("nil")
Key = MissingOp("key")
)
func (m MemberOp) Format(f fmt.State, r rune) { fmt.Fprint(f, ".", string(m)) }
func (i IndexOp) Format(f fmt.State, r rune) { fmt.Fprintf(f, "[%v]", int(i)) }
func (e EntryOp) Format(f fmt.State, r rune) { fmt.Fprintf(f, "[%v]", e.Key) }
func (p Path) with(op, reference, value interface{}) Path {
r := make(Path, len(p)+1)
copy(r, p)
r[len(p)] = Fragment{op, reference, value}
return r
}
// Member returns a new Path with a member access fragment appended.
func (p Path) Member(name string, reference, value interface{}) Path {
return p.with(MemberOp(name), reference, value)
}
// Length returns a new Path with a length query fragment appended.
func (p Path) Length(reference, value interface{}) Path { return p.with(Length, reference, value) }
// Type returns a new Path with a type query fragment appended.
func (p Path) Type(reference, value interface{}) Path { return p.with(Type, reference, value) }
// Nil returns a new Path with a nil query fragment appended.
func (p Path) Nil(reference, value interface{}) Path { return p.with(Nil, reference, value) }
// Missing returns a new Path with a missing value fragment appended.
func (p Path) Missing(reference, value interface{}) Path { return p.with(Key, reference, value) }
// Index returns a new Path with an array/slice index fragment appended.
func (p Path) Index(i int, reference, value interface{}) Path {
return p.with(IndexOp(i), reference, value)
}
// Entry returns a new Path with a map entry fragment appended.
func (p Path) Entry(key, reference, value interface{}) Path {
return p.with(EntryOp{key}, reference, value)
}
// Diff returns a new Path with a terminal diff fragment appended.
func (p Path) Diff(reference, value interface{}) Path { return p.with(nil, reference, value) }
func (p Path) Format(f fmt.State, r rune) {
if len(p) == 0 {
return
}
last := p[len(p)-1]
remains := p[:len(p)-1]
if last.Operation != nil {
fmt.Fprint(f, last.Operation, " ")
}
fmt.Fprint(f, "«", last.Reference, "» != «", last.Value, "»")
if len(remains) > 0 {
fmt.Fprint(f, " for v")
for _, e := range remains {
fmt.Fprint(f, e.Operation)
}
}
}