Files
path/parse.go

154 lines
2.9 KiB
Go
Raw Permalink Normal View History

2023-04-20 18:01:36 +00:00
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) {
typ := reflect.TypeOf(t)
if typ.Kind() == reflect.Slice {
typ = typ.Elem()
}
if typ.Kind() == reflect.Pointer {
typ = typ.Elem()
}
// TODO: Consider attempting to convert to string in default case
switch typ.Kind() { //nolint:exhaustive
case reflect.Int:
return parseInt(str)
case reflect.Int64:
return strconv.ParseInt(str, 10, 64)
case reflect.Uint:
return parseUint(str)
case reflect.Uint64:
return strconv.ParseUint(str, 10, 64)
case reflect.Float32:
return parseFloat32(str)
case reflect.Float64:
return strconv.ParseFloat(str, 64)
case reflect.String:
return str, nil
case reflect.Bool:
return strconv.ParseBool(str)
case reflect.Struct:
switch typ {
case reflect.TypeOf(time.Time{}):
return parseTime(str)
case reflect.TypeOf(civil.Date{}):
return civil.ParseDate(str)
}
}
return nil, jsrest.Errorf(jsrest.ErrBadRequest, "%T (%w)", t, ErrUnsupportedType)
}
func parseInt(str string) (int, error) {
val, err := strconv.ParseInt(str, 10, strconv.IntSize)
return int(val), err
}
func parseUint(str string) (uint, error) {
val, err := strconv.ParseUint(str, 10, strconv.IntSize)
return uint(val), err
}
func parseFloat32(str string) (float32, error) {
val, err := strconv.ParseFloat(str, 32)
return float32(val), err
}
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) {
if strings.ToLower(str) == "now" {
return &timeVal{
time: time.Now(),
precision: 1 * time.Nanosecond,
}, nil
}
for _, format := range timeFormats {
tm, err := time.Parse(format.format, str)
if err != nil {
continue
}
return &timeVal{
time: tm,
precision: format.precision,
}, nil
}
i, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return nil, jsrest.Errorf(jsrest.ErrBadRequest, "%s (%w)", str, ErrUnknownTimeFormat)
}
// UNIX Seconds: 2969-05-03
// UNIX Millis: 1971-01-01
// Intended to give us a wide range of useful values in both schemes
if i > 31536000000 {
return &timeVal{
time: time.UnixMilli(i),
precision: 1 * time.Millisecond,
}, nil
}
return &timeVal{
time: time.Unix(i, 0),
precision: 1 * time.Second,
}, nil
}