// Copyright 2016 The OPA Authors. All rights reserved. // Use of this source code is governed by an Apache2 // license that can be found in the LICENSE file. package util import ( "encoding/json" "fmt" "math/big" ) // Compare returns 0 if a equals b, -1 if a is less than b, and 1 if b is than a. // // For comparison between values of different types, the following ordering is used: // nil < bool < int, float64 < string < []any < map[string]any. Slices and maps // are compared recursively. If one slice or map is a subset of the other slice or map // it is considered "less than". Nil is always equal to nil. func Compare(a, b any) int { aSortOrder := sortOrder(a) bSortOrder := sortOrder(b) if aSortOrder < bSortOrder { return -1 } else if bSortOrder < aSortOrder { return 1 } switch a := a.(type) { case nil: return 0 case bool: switch b := b.(type) { case bool: if a == b { return 0 } if !a { return -1 } return 1 } case json.Number: switch b := b.(type) { case json.Number: return compareJSONNumber(a, b) } case int: switch b := b.(type) { case int: if a == b { return 0 } else if a < b { return -1 } return 1 } case float64: switch b := b.(type) { case float64: if a == b { return 0 } else if a < b { return -1 } return 1 } case string: switch b := b.(type) { case string: if a == b { return 0 } else if a < b { return -1 } return 1 } case []any: switch b := b.(type) { case []any: bLen := len(b) aLen := len(a) minLen := min(bLen, aLen) for i := range minLen { cmp := Compare(a[i], b[i]) if cmp != 0 { return cmp } } if aLen == bLen { return 0 } else if aLen < bLen { return -1 } return 1 } case map[string]any: switch b := b.(type) { case map[string]any: aKeys := KeysSorted(a) bKeys := KeysSorted(b) aLen := len(aKeys) bLen := len(bKeys) minLen := min(bLen, aLen) for i := range minLen { if aKeys[i] < bKeys[i] { return -1 } else if bKeys[i] < aKeys[i] { return 1 } aVal := a[aKeys[i]] bVal := b[bKeys[i]] cmp := Compare(aVal, bVal) if cmp != 0 { return cmp } } if aLen == bLen { return 0 } else if aLen < bLen { return -1 } return 1 } } panic(fmt.Sprintf("illegal arguments of type %T and type %T", a, b)) } const ( nilSort = iota boolSort = iota numberSort = iota stringSort = iota arraySort = iota objectSort = iota ) func compareJSONNumber(a, b json.Number) int { bigA, ok := new(big.Float).SetString(string(a)) if !ok { panic("illegal value") } bigB, ok := new(big.Float).SetString(string(b)) if !ok { panic("illegal value") } return bigA.Cmp(bigB) } func sortOrder(v any) int { switch v.(type) { case nil: return nilSort case bool: return boolSort case json.Number: return numberSort case int: return numberSort case float64: return numberSort case string: return stringSort case []any: return arraySort case map[string]any: return objectSort } panic(fmt.Sprintf("illegal argument of type %T", v)) }