Files
path/cover.html
Ian Gulliver 6e64921fd0 sync
2023-05-15 21:13:00 -07:00

1137 lines
37 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>path: Go Coverage Report</title>
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }
</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">
<option value="file0">github.com/gopatchy/path/equal.go (100.0%)</option>
<option value="file1">github.com/gopatchy/path/greater.go (92.9%)</option>
<option value="file2">github.com/gopatchy/path/greaterequal.go (93.3%)</option>
<option value="file3">github.com/gopatchy/path/hasprefix.go (80.0%)</option>
<option value="file4">github.com/gopatchy/path/in.go (100.0%)</option>
<option value="file5">github.com/gopatchy/path/less.go (92.9%)</option>
<option value="file6">github.com/gopatchy/path/lessequal.go (93.3%)</option>
<option value="file7">github.com/gopatchy/path/merge.go (84.2%)</option>
<option value="file8">github.com/gopatchy/path/op.go (82.1%)</option>
<option value="file9">github.com/gopatchy/path/parse.go (94.6%)</option>
<option value="file10">github.com/gopatchy/path/path.go (87.0%)</option>
<option value="file11">github.com/gopatchy/path/slice.go (100.0%)</option>
<option value="file12">github.com/gopatchy/path/sort.go (73.5%)</option>
</select>
</div>
<div id="legend">
<span>not tracked</span>
<span class="cov0">no coverage</span>
<span class="cov1">low coverage</span>
<span class="cov2">*</span>
<span class="cov3">*</span>
<span class="cov4">*</span>
<span class="cov5">*</span>
<span class="cov6">*</span>
<span class="cov7">*</span>
<span class="cov8">*</span>
<span class="cov9">*</span>
<span class="cov10">high coverage</span>
</div>
</div>
<div id="content">
<pre class="file" id="file0" style="display: none">package path
import "time"
func Equal(obj any, path string, matchStr string) (bool, error) <span class="cov7" title="55">{
return op(obj, path, matchStr, equal)
}</span>
func equal(obj, match any, _ string) bool <span class="cov10" title="203">{
switch objt := obj.(type) </span>{
case time.Time:<span class="cov6" title="28">
tm := match.(*timeVal)
// TODO: Replace Truncate() with a timezone-aware version
return tm.time.Equal(objt.Truncate(tm.precision))</span>
default:<span class="cov9" title="175">
return obj == match</span>
}
}
</pre>
<pre class="file" id="file1" style="display: none">package path
import (
"time"
"cloud.google.com/go/civil"
)
func Greater(obj any, path string, matchStr string) (bool, error) <span class="cov8" title="40">{
return op(obj, path, matchStr, greater)
}</span>
func greater(obj, match any, _ string) bool <span class="cov10" title="64">{
switch objt := obj.(type) </span>{
case int:<span class="cov5" title="8">
return objt &gt; match.(int)</span>
case int64:<span class="cov5" title="8">
return objt &gt; match.(int64)</span>
case uint:<span class="cov5" title="8">
return objt &gt; match.(uint)</span>
case uint64:<span class="cov5" title="8">
return objt &gt; match.(uint64)</span>
case float32:<span class="cov4" title="5">
return objt &gt; match.(float32)</span>
case float64:<span class="cov4" title="5">
return objt &gt; match.(float64)</span>
case string:<span class="cov4" title="5">
return objt &gt; match.(string)</span>
case bool:<span class="cov4" title="5">
return objt &amp;&amp; !match.(bool)</span>
case time.Time:<span class="cov4" title="6">
tm := match.(*timeVal)
return objt.Truncate(tm.precision).After(tm.time)</span>
case civil.Date:<span class="cov4" title="6">
return objt.After(match.(civil.Date))</span>
default:<span class="cov0" title="0">
panic(obj)</span>
}
}
</pre>
<pre class="file" id="file2" style="display: none">package path
import (
"time"
"cloud.google.com/go/civil"
)
func GreaterEqual(obj any, path string, matchStr string) (bool, error) <span class="cov9" title="60">{
return op(obj, path, matchStr, greaterEqual)
}</span>
func greaterEqual(obj, match any, _ string) bool <span class="cov10" title="94">{
switch objt := obj.(type) </span>{
case int:<span class="cov5" title="12">
return objt &gt;= match.(int)</span>
case int64:<span class="cov5" title="12">
return objt &gt;= match.(int64)</span>
case uint:<span class="cov5" title="12">
return objt &gt;= match.(uint)</span>
case uint64:<span class="cov5" title="12">
return objt &gt;= match.(uint64)</span>
case float32:<span class="cov4" title="7">
return objt &gt;= match.(float32)</span>
case float64:<span class="cov4" title="7">
return objt &gt;= match.(float64)</span>
case string:<span class="cov4" title="7">
return objt &gt;= match.(string)</span>
case bool:<span class="cov4" title="7">
return objt || objt == match.(bool)</span>
case time.Time:<span class="cov5" title="9">
tm := match.(*timeVal)
trunc := objt.Truncate(tm.precision)
return trunc.Equal(tm.time) || trunc.After(tm.time)</span>
case civil.Date:<span class="cov5" title="9">
return objt == match.(civil.Date) || objt.After(match.(civil.Date))</span>
default:<span class="cov0" title="0">
panic(obj)</span>
}
}
</pre>
<pre class="file" id="file3" style="display: none">package path
import (
"strconv"
"strings"
"time"
"cloud.google.com/go/civil"
)
func HasPrefix(obj any, path string, matchStr string) (bool, error) <span class="cov9" title="18">{
return op(obj, path, matchStr, hasPrefix)
}</span>
func hasPrefix(obj, match any, matchStr string) bool <span class="cov10" title="19">{
var objStr string
switch objt := obj.(type) </span>{
case int:<span class="cov3" title="2">
objStr = strconv.FormatInt(int64(objt), 10)</span>
case int64:<span class="cov3" title="2">
objStr = strconv.FormatInt(objt, 10)</span>
case uint:<span class="cov3" title="2">
objStr = strconv.FormatUint(uint64(objt), 10)</span>
case uint64:<span class="cov3" title="2">
objStr = strconv.FormatUint(objt, 10)</span>
case float32:<span class="cov3" title="2">
objStr = strconv.FormatFloat(float64(objt), 'f', -1, 32)</span>
case float64:<span class="cov3" title="2">
objStr = strconv.FormatFloat(objt, 'f', -1, 64)</span>
case string:<span class="cov5" title="5">
objStr = objt</span>
case bool:<span class="cov3" title="2">
objStr = strconv.FormatBool(objt)</span>
case time.Time:<span class="cov0" title="0">
objStr = objt.String()</span>
case civil.Date:<span class="cov0" title="0">
objStr = objt.String()</span>
default:<span class="cov0" title="0">
panic(obj)</span>
}
<span class="cov10" title="19">return strings.HasPrefix(objStr, matchStr)</span>
}
</pre>
<pre class="file" id="file4" style="display: none">package path
func In(obj any, path string, matchStr string) (bool, error) <span class="cov10" title="40">{
return opList(obj, path, matchStr, equal)
}</span>
</pre>
<pre class="file" id="file5" style="display: none">package path
import (
"time"
"cloud.google.com/go/civil"
)
func Less(obj any, path string, matchStr string) (bool, error) <span class="cov9" title="40">{
return op(obj, path, matchStr, less)
}</span>
func less(obj, match any, _ string) bool <span class="cov10" title="58">{
switch objt := obj.(type) </span>{
case int:<span class="cov4" title="6">
return objt &lt; match.(int)</span>
case int64:<span class="cov4" title="6">
return objt &lt; match.(int64)</span>
case uint:<span class="cov4" title="6">
return objt &lt; match.(uint)</span>
case uint64:<span class="cov4" title="6">
return objt &lt; match.(uint64)</span>
case float32:<span class="cov4" title="6">
return objt &lt; match.(float32)</span>
case float64:<span class="cov4" title="6">
return objt &lt; match.(float64)</span>
case string:<span class="cov4" title="6">
return objt &lt; match.(string)</span>
case bool:<span class="cov4" title="6">
return !objt &amp;&amp; match.(bool)</span>
case time.Time:<span class="cov4" title="5">
tm := match.(*timeVal)
return objt.Truncate(tm.precision).Before(tm.time)</span>
case civil.Date:<span class="cov4" title="5">
return objt.Before(match.(civil.Date))</span>
default:<span class="cov0" title="0">
panic(obj)</span>
}
}
</pre>
<pre class="file" id="file6" style="display: none">package path
import (
"time"
"cloud.google.com/go/civil"
)
func LessEqual(obj any, path string, matchStr string) (bool, error) <span class="cov9" title="60">{
return op(obj, path, matchStr, lessEqual)
}</span>
func lessEqual(obj, match any, _ string) bool <span class="cov10" title="81">{
switch objt := obj.(type) </span>{
case int:<span class="cov5" title="8">
return objt &lt;= match.(int)</span>
case int64:<span class="cov5" title="8">
return objt &lt;= match.(int64)</span>
case uint:<span class="cov5" title="8">
return objt &lt;= match.(uint)</span>
case uint64:<span class="cov5" title="8">
return objt &lt;= match.(uint64)</span>
case float32:<span class="cov5" title="9">
return objt &lt;= match.(float32)</span>
case float64:<span class="cov5" title="9">
return objt &lt;= match.(float64)</span>
case string:<span class="cov5" title="9">
return objt &lt;= match.(string)</span>
case bool:<span class="cov5" title="8">
return !objt || objt == match.(bool)</span>
case time.Time:<span class="cov4" title="7">
tm := match.(*timeVal)
trunc := objt.Truncate(tm.precision)
return trunc.Equal(tm.time) || trunc.Before(tm.time)</span>
case civil.Date:<span class="cov4" title="7">
return objt == match.(civil.Date) || objt.Before(match.(civil.Date))</span>
default:<span class="cov0" title="0">
panic(obj)</span>
}
}
</pre>
<pre class="file" id="file7" style="display: none">package path
import (
"encoding/json"
"reflect"
"github.com/gopatchy/jsrest"
)
func Merge(to, from any) <span class="cov4" title="4">{
MergeValue(reflect.ValueOf(to), reflect.ValueOf(from))
}</span>
func MergeValue(to, from reflect.Value) <span class="cov5" title="6">{
to = reflect.Indirect(to)
from = reflect.Indirect(from)
for i := 0; i &lt; to.NumField(); i++ </span><span class="cov10" title="28">{
toField := to.Field(i)
fromField := from.Field(i)
if fromField.IsZero() </span><span class="cov9" title="22">{
continue</span>
}
<span class="cov5" title="6">if reflect.Indirect(fromField).Kind() == reflect.Struct </span><span class="cov2" title="2">{
MergeValue(toField, fromField)
continue</span>
}
<span class="cov4" title="4">toField.Set(fromField)</span>
}
}
func MergeMap(to any, from map[string]any) error <span class="cov1" title="1">{
m, err := ToMap(to)
if err != nil </span><span class="cov0" title="0">{
return jsrest.Errorf(jsrest.ErrInternalServerError, "converting to map failed (%w)", err)
}</span>
<span class="cov1" title="1">MergeMaps(m, from)
return FromMap(to, m)</span>
}
func MergeMaps(to map[string]any, from map[string]any) <span class="cov2" title="2">{
for k, v := range from </span><span class="cov4" title="4">{
if vMap, isMap := v.(map[string]any); isMap </span><span class="cov1" title="1">{
if _, ok := to[k].(map[string]any); !ok </span><span class="cov0" title="0">{
// Either key doesn't exist or it's a different type
// If different type, error will happen during json decode
to[k] = map[string]any{}
}</span>
<span class="cov1" title="1">MergeMaps(to[k].(map[string]any), vMap)</span>
} else<span class="cov3" title="3"> {
to[k] = v
}</span>
}
}
func ToMap(from any) (map[string]any, error) <span class="cov1" title="1">{
js, err := json.Marshal(from)
if err != nil </span><span class="cov0" title="0">{
return nil, jsrest.Errorf(jsrest.ErrInternalServerError, "json marshal failed (%w)", err)
}</span>
<span class="cov1" title="1">ret := map[string]any{}
err = json.Unmarshal(js, &amp;ret)
if err != nil </span><span class="cov0" title="0">{
return nil, jsrest.Errorf(jsrest.ErrInternalServerError, "json unmarshal failed (%w)", err)
}</span>
<span class="cov1" title="1">return ret, nil</span>
}
func FromMap(to any, from map[string]any) error <span class="cov1" title="1">{
js, err := json.Marshal(from)
if err != nil </span><span class="cov0" title="0">{
return jsrest.Errorf(jsrest.ErrInternalServerError, "json marshal failed (%w)", err)
}</span>
<span class="cov1" title="1">err = json.Unmarshal(js, to)
if err != nil </span><span class="cov0" title="0">{
return jsrest.Errorf(jsrest.ErrInternalServerError, "json unmarshal failed (%w)", err)
}</span>
<span class="cov1" title="1">return nil</span>
}
</pre>
<pre class="file" id="file8" style="display: none">package path
import (
"strings"
)
func op(obj any, path string, matchStr string, cb func(any, any, string) bool) (bool, error) <span class="cov10" title="273">{
objVal, err := Get(obj, path)
if err != nil </span><span class="cov0" title="0">{
return false, err
}</span>
<span class="cov10" title="273">matchVal, err := parse(matchStr, objVal)
if err != nil </span><span class="cov0" title="0">{
return false, err
}</span>
<span class="cov10" title="273">if isSlice(objVal) </span><span class="cov8" title="124">{
return anyTrue(objVal, func(x any, _ int) bool </span><span class="cov9" title="245">{ return cb(x, matchVal, matchStr) }</span>), nil
}
<span class="cov9" title="149">return cb(objVal, matchVal, matchStr), nil</span>
}
func opList(obj any, path string, matchStr string, cb func(any, any, string) bool) (bool, error) <span class="cov6" title="40">{
objVal, err := Get(obj, path)
if err != nil </span><span class="cov0" title="0">{
return false, err
}</span>
<span class="cov6" title="40">if objVal == nil </span><span class="cov0" title="0">{
return false, nil
}</span>
<span class="cov6" title="40">matchVal := []any{}
matchParts := strings.Split(matchStr, ",")
for _, matchPart := range matchParts </span><span class="cov8" title="98">{
matchTmp, err := parse(matchPart, objVal)
if err != nil </span><span class="cov0" title="0">{
return false, err
}</span>
<span class="cov8" title="98">matchVal = append(matchVal, matchTmp)</span>
}
<span class="cov6" title="40">return anyTrue(matchVal, func(y any, i int) bool </span><span class="cov7" title="78">{
str := matchParts[i]
if isSlice(objVal) </span><span class="cov6" title="39">{
return anyTrue(objVal, func(x any, _ int) bool </span><span class="cov8" title="86">{ return cb(x, y, str) }</span>)
}
<span class="cov6" title="39">return cb(objVal, y, str)</span>
}), nil
}
</pre>
<pre class="file" id="file9" style="display: none">package path
import (
"errors"
"reflect"
"strconv"
"strings"
"time"
"cloud.google.com/go/civil"
"github.com/gopatchy/jsrest"
)
type timeVal struct {
time time.Time
precision time.Duration
}
var (
ErrUnsupportedType = errors.New("unsupported type")
ErrUnknownTimeFormat = errors.New("unknown time format")
)
func parse(str string, t any) (any, error) <span class="cov10" title="380">{
typ := reflect.TypeOf(t)
if typ.Kind() == reflect.Slice </span><span class="cov8" title="173">{
typ = typ.Elem()
}</span>
<span class="cov10" title="380">if typ.Kind() == reflect.Pointer </span><span class="cov2" title="2">{
typ = typ.Elem()
}</span>
// TODO: Consider attempting to convert to string in default case
<span class="cov10" title="380">switch typ.Kind() </span>{ //nolint:exhaustive
case reflect.Int:<span class="cov6" title="40">
return parseInt(str)</span>
case reflect.Int64:<span class="cov6" title="37">
return strconv.ParseInt(str, 10, 64)</span>
case reflect.Uint:<span class="cov6" title="36">
return parseUint(str)</span>
case reflect.Uint64:<span class="cov6" title="36">
return strconv.ParseUint(str, 10, 64)</span>
case reflect.Float32:<span class="cov6" title="36">
return parseFloat32(str)</span>
case reflect.Float64:<span class="cov6" title="36">
return strconv.ParseFloat(str, 64)</span>
case reflect.String:<span class="cov6" title="39">
return str, nil</span>
case reflect.Bool:<span class="cov6" title="38">
return strconv.ParseBool(str)</span>
case reflect.Struct:<span class="cov7" title="82">
switch typ </span>{
case reflect.TypeOf(time.Time{}):<span class="cov6" title="48">
return parseTime(str)</span>
case reflect.TypeOf(civil.Date{}):<span class="cov6" title="34">
return civil.ParseDate(str)</span>
}
}
<span class="cov0" title="0">return nil, jsrest.Errorf(jsrest.ErrBadRequest, "%T (%w)", t, ErrUnsupportedType)</span>
}
func parseInt(str string) (int, error) <span class="cov6" title="40">{
val, err := strconv.ParseInt(str, 10, strconv.IntSize)
return int(val), err
}</span>
func parseUint(str string) (uint, error) <span class="cov6" title="36">{
val, err := strconv.ParseUint(str, 10, strconv.IntSize)
return uint(val), err
}</span>
func parseFloat32(str string) (float32, error) <span class="cov6" title="36">{
val, err := strconv.ParseFloat(str, 32)
return float32(val), err
}</span>
type timeFormat struct {
format string
precision time.Duration
}
var timeFormats = []timeFormat{
{
format: "2006-01-02-07:00",
precision: 24 * time.Hour,
// TODO: Support field annotation to change start vs end of day
// TODO: Support timezone context passed down to allow naked date
},
{
format: "2006-01-02T15:04:05Z",
precision: 1 * time.Second,
},
{
format: "2006-01-02T15:04:05-07:00",
precision: 1 * time.Second,
},
}
func parseTime(str string) (*timeVal, error) <span class="cov6" title="48">{
if strings.ToLower(str) == "now" </span><span class="cov1" title="1">{
return &amp;timeVal{
time: time.Now(),
precision: 1 * time.Nanosecond,
}, nil
}</span>
<span class="cov6" title="47">for _, format := range timeFormats </span><span class="cov8" title="103">{
tm, err := time.Parse(format.format, str)
if err != nil </span><span class="cov7" title="63">{
continue</span>
}
<span class="cov6" title="40">return &amp;timeVal{
time: tm,
precision: format.precision,
}, nil</span>
}
<span class="cov3" title="7">i, err := strconv.ParseInt(str, 10, 64)
if err != nil </span><span class="cov0" title="0">{
return nil, jsrest.Errorf(jsrest.ErrBadRequest, "%s (%w)", str, ErrUnknownTimeFormat)
}</span>
// UNIX Seconds: 2969-05-03
// UNIX Millis: 1971-01-01
// Intended to give us a wide range of useful values in both schemes
<span class="cov3" title="7">if i &gt; 31536000000 </span><span class="cov3" title="4">{
return &amp;timeVal{
time: time.UnixMilli(i),
precision: 1 * time.Millisecond,
}, nil
}</span>
<span class="cov2" title="3">return &amp;timeVal{
time: time.Unix(i, 0),
precision: 1 * time.Second,
}, nil</span>
}
</pre>
<pre class="file" id="file10" style="display: none">package path
import (
"errors"
"fmt"
"reflect"
"sort"
"strings"
"time"
"cloud.google.com/go/civil"
"github.com/gopatchy/jsrest"
"golang.org/x/exp/slices"
)
type WalkCallback func(string, []string, reflect.StructField)
var (
TimeTimeType = reflect.TypeOf(time.Time{})
CivilDateType = reflect.TypeOf(civil.Date{})
ErrNotAStruct = errors.New("not a struct")
ErrUnknownFieldName = errors.New("unknown field name")
)
func Get(obj any, path string) (any, error) <span class="cov6" title="382">{
v, err := GetValue(reflect.ValueOf(obj), path)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov6" title="382">return v.Interface(), nil</span>
}
func GetValue(v reflect.Value, path string) (reflect.Value, error) <span class="cov6" title="382">{
parts := strings.Split(path, ".")
return getRecursive(v, parts, []string{})
}</span>
func getRecursive(v reflect.Value, parts []string, prev []string) (reflect.Value, error) <span class="cov7" title="771">{
if v.Kind() == reflect.Pointer </span><span class="cov6" title="388">{
if v.IsNil() </span><span class="cov1" title="2">{
v = reflect.Zero(v.Type().Elem())
}</span> else<span class="cov6" title="386"> {
v = reflect.Indirect(v)
}</span>
}
<span class="cov7" title="771">if len(parts) == 0 </span><span class="cov6" title="382">{
return v, nil
}</span>
<span class="cov6" title="389">if v.Kind() != reflect.Struct </span><span class="cov0" title="0">{
return reflect.Value{}, jsrest.Errorf(jsrest.ErrBadRequest, "%s (%w)", strings.Join(prev, "."), ErrNotAStruct)
}</span>
<span class="cov6" title="389">part := parts[0]
sub, found := getField(v, part)
if !found </span><span class="cov0" title="0">{
return reflect.Value{}, jsrest.Errorf(jsrest.ErrBadRequest, "%s (%w)", errorPath(prev, part), ErrUnknownFieldName)
}</span>
<span class="cov6" title="389">newPrev := []string{}
newPrev = append(newPrev, prev...)
newPrev = append(newPrev, part)
return getRecursive(sub, parts[1:], newPrev)</span>
}
func getField(v reflect.Value, name string) (reflect.Value, bool) <span class="cov6" title="398">{
field, found := getStructField(v.Type(), name)
if !found </span><span class="cov0" title="0">{
return reflect.Value{}, false
}</span>
<span class="cov6" title="398">return v.FieldByName(field.Name), true</span>
}
func Set(obj any, path string, val string) error <span class="cov2" title="6">{
return SetValue(reflect.ValueOf(obj), path, val)
}</span>
func SetValue(v reflect.Value, path string, val string) error <span class="cov2" title="6">{
parts := strings.Split(path, ".")
return setRecursive(v, parts, []string{}, val)
}</span>
func setRecursive(v reflect.Value, parts []string, prev []string, val string) error <span class="cov3" title="15">{
if v.Kind() == reflect.Pointer </span><span class="cov3" title="9">{
if v.IsNil() </span><span class="cov1" title="2">{
v.Set(reflect.New(v.Type().Elem()))
}</span>
<span class="cov3" title="9">v = reflect.Indirect(v)</span>
}
<span class="cov3" title="15">if len(parts) == 0 </span><span class="cov2" title="6">{
n, err := parse(val, v.Interface())
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov2" title="6">if _, ok := n.(*timeVal); ok </span><span class="cov1" title="1">{
n = n.(*timeVal).time
}</span>
<span class="cov2" title="6">v.Set(reflect.ValueOf(n))
return nil</span>
}
<span class="cov3" title="9">if v.Kind() != reflect.Struct </span><span class="cov0" title="0">{
return jsrest.Errorf(jsrest.ErrBadRequest, "%s (%w)", strings.Join(prev, "."), ErrNotAStruct)
}</span>
<span class="cov3" title="9">part := parts[0]
sub, found := getField(v, part)
if !found </span><span class="cov0" title="0">{
return jsrest.Errorf(jsrest.ErrBadRequest, "%s (%w)", errorPath(prev, part), ErrUnknownFieldName)
}</span>
<span class="cov3" title="9">newPrev := []string{}
newPrev = append(newPrev, prev...)
newPrev = append(newPrev, part)
return setRecursive(sub, parts[1:], newPrev, val)</span>
}
func List(obj any) []string <span class="cov1" title="1">{
return ListType(reflect.TypeOf(obj))
}</span>
func ListType(t reflect.Type) []string <span class="cov1" title="1">{
list := []string{}
WalkType(t, func(path string, _ []string, field reflect.StructField) </span><span class="cov2" title="7">{
t := MaybeIndirectType(field.Type)
if t.Kind() == reflect.Struct &amp;&amp; t != TimeTimeType &amp;&amp; t != CivilDateType </span><span class="cov1" title="1">{
return
}</span>
<span class="cov2" title="6">list = append(list, path)</span>
})
<span class="cov1" title="1">sort.Strings(list)
return list</span>
}
func GetFieldType(t reflect.Type, path string) reflect.Type <span class="cov1" title="2">{
parts := strings.Split(path, ".")
for _, part := range parts </span><span class="cov2" title="3">{
field, found := getStructField(MaybeIndirectType(t), part)
if !found </span><span class="cov0" title="0">{
return nil
}</span>
<span class="cov2" title="3">t = field.Type</span>
}
<span class="cov1" title="2">return t</span>
}
func FindTagValueType(t reflect.Type, key, value string) (string, bool) <span class="cov1" title="2">{
ret := ""
WalkType(t, func(path string, _ []string, field reflect.StructField) </span><span class="cov3" title="14">{
tag, found := field.Tag.Lookup(key)
if !found </span><span class="cov3" title="10">{
return
}</span>
<span class="cov2" title="4">parts := strings.Split(tag, ",")
if slices.Contains(parts, value) </span><span class="cov1" title="2">{
ret = path
}</span>
})
<span class="cov1" title="2">return ret, ret != ""</span>
}
func Walk(obj any, cb WalkCallback) <span class="cov0" title="0">{
WalkType(reflect.TypeOf(obj), cb)
}</span>
func WalkType(t reflect.Type, cb WalkCallback) <span class="cov2" title="3">{
walkRecursive(MaybeIndirectType(t), cb, []string{})
}</span>
func walkRecursive(t reflect.Type, cb WalkCallback, prev []string) <span class="cov3" title="9">{
for i := 0; i &lt; t.NumField(); i++ </span><span class="cov4" title="24">{
sub := t.Field(i)
newPrev := []string{}
newPrev = append(newPrev, prev...)
if !sub.Anonymous </span><span class="cov4" title="21">{
newPrev = append(newPrev, FieldName(sub))
}</span>
<span class="cov4" title="24">t := MaybeIndirectType(sub.Type)
if len(newPrev) &gt; 0 </span><span class="cov4" title="21">{
cb(strings.Join(newPrev, "."), newPrev, sub)
}</span>
<span class="cov4" title="24">if t.Kind() == reflect.Struct &amp;&amp; t != TimeTimeType &amp;&amp; t != CivilDateType </span><span class="cov2" title="6">{
walkRecursive(t, cb, newPrev)
}</span>
}
}
func getStructField(t reflect.Type, name string) (reflect.StructField, bool) <span class="cov6" title="401">{
name = strings.ToLower(name)
return t.FieldByNameFunc(func(iterName string) bool </span><span class="cov9" title="8955">{
iterField, iterOK := t.FieldByName(iterName)
if !iterOK </span><span class="cov0" title="0">{
panic(iterName)</span>
}
<span class="cov9" title="8955">return strings.ToLower(FieldName(iterField)) == name</span>
})
}
func errorPath(prev []string, part string) string <span class="cov0" title="0">{
if len(prev) == 0 </span><span class="cov0" title="0">{
return part
}</span>
<span class="cov0" title="0">return fmt.Sprintf("%s.%s", strings.Join(prev, "."), part)</span>
}
func FieldName(field reflect.StructField) string <span class="cov10" title="8976">{
tag := field.Tag.Get("json")
if tag != "" </span><span class="cov7" title="797">{
if tag == "-" </span><span class="cov0" title="0">{
return ""
}</span>
<span class="cov7" title="797">parts := strings.SplitN(tag, ",", 2)
return parts[0]</span>
}
<span class="cov9" title="8179">return field.Name</span>
}
func MaybeIndirectType(t reflect.Type) reflect.Type <span class="cov4" title="37">{
if t.Kind() == reflect.Pointer </span><span class="cov3" title="10">{
return t.Elem()
}</span>
<span class="cov4" title="27">return t</span>
}
</pre>
<pre class="file" id="file11" style="display: none">package path
import "reflect"
func isSlice(v any) bool <span class="cov9" title="351">{
return reflect.TypeOf(v).Kind() == reflect.Slice
}</span>
func anyTrue(v any, cb func(any, int) bool) bool <span class="cov8" title="203">{
val := reflect.ValueOf(v)
for i := 0; i &lt; val.Len(); i++ </span><span class="cov10" title="410">{
sub := val.Index(i)
if sub.Kind() == reflect.Pointer &amp;&amp; sub.IsNil() </span><span class="cov1" title="1">{
continue</span>
}
<span class="cov9" title="409">sub = reflect.Indirect(sub)
if cb(sub.Interface(), i) </span><span class="cov7" title="102">{
return true
}</span>
}
<span class="cov7" title="101">return false</span>
}
</pre>
<pre class="file" id="file12" style="display: none">package path
import (
"errors"
"reflect"
"sort"
"time"
"cloud.google.com/go/civil"
"github.com/gopatchy/jsrest"
)
func Sort(objs any, path string) error <span class="cov7" title="11">{
as := newAnySlice(objs, path)
sort.Stable(as)
return as.err
}</span>
func SortReverse(objs any, path string) error <span class="cov1" title="1">{
as := newAnySlice(objs, path)
sort.Stable(sort.Reverse(as))
return as.err
}</span>
type anySlice struct {
path string
slice reflect.Value
swapper func(i, j int)
err error
}
var ErrUnsupportedSortType = errors.New("unsupported _sort type")
func newAnySlice(objs any, path string) *anySlice <span class="cov7" title="12">{
return &amp;anySlice{
path: path,
slice: reflect.ValueOf(objs),
swapper: reflect.Swapper(objs),
}
}</span>
func (as *anySlice) Len() int <span class="cov7" title="12">{
return as.slice.Len()
}</span>
func (as *anySlice) Less(i, j int) bool <span class="cov10" title="34">{
v1, err := Get(as.slice.Index(i).Interface(), as.path)
if err != nil </span><span class="cov0" title="0">{
as.err = err
// We have to obey the Less() contract even in error cases
return i &lt; j
}</span>
<span class="cov10" title="34">v2, err := Get(as.slice.Index(j).Interface(), as.path)
if err != nil </span><span class="cov0" title="0">{
as.err = err
return i &lt; j
}</span>
<span class="cov10" title="34">switch </span>{
case v1 == nil &amp;&amp; v2 == nil:<span class="cov0" title="0">
return false</span>
case v1 == nil:<span class="cov0" title="0">
return true</span>
case v2 == nil:<span class="cov0" title="0">
return false</span>
}
<span class="cov10" title="34">switch t1 := v1.(type) </span>{
case int:<span class="cov6" title="8">
return t1 &lt; v2.(int)</span>
case int64:<span class="cov3" title="3">
return t1 &lt; v2.(int64)</span>
case uint:<span class="cov3" title="3">
return t1 &lt; v2.(uint)</span>
case uint64:<span class="cov3" title="3">
return t1 &lt; v2.(uint64)</span>
case float32:<span class="cov3" title="3">
return t1 &lt; v2.(float32)</span>
case float64:<span class="cov3" title="3">
return t1 &lt; v2.(float64)</span>
case string:<span class="cov3" title="3">
return t1 &lt; v2.(string)</span>
case bool:<span class="cov2" title="2">
return !t1 &amp;&amp; v2.(bool)</span>
case time.Time:<span class="cov3" title="3">
return t1.Before(v2.(time.Time))</span>
case civil.Date:<span class="cov3" title="3">
return t1.Before(v2.(civil.Date))</span>
default:<span class="cov0" title="0">
as.err = jsrest.Errorf(jsrest.ErrBadRequest, "%s: %T (%w)", as.path, t1, ErrUnsupportedSortType)
return i &lt; j</span>
}
}
func (as *anySlice) Swap(i, j int) <span class="cov8" title="21">{
as.swapper(i, j)
}</span>
</pre>
</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>