feat(12.2-01): add lagoon.Date and lagoon.TimeOfDay with Fill and required support
- Date (DATE) and TimeOfDay (TIME) with Scanner, Valuer, JSON and text forms - Fill falls back to encoding.TextUnmarshaler for string sources after every existing conversion, so time.Time and the new types fill from JSON - required treats a zero time.Time, Date or TimeOfDay as empty - lagoon README, models and casts-and-validation docs
This commit is contained in:
@@ -2,6 +2,7 @@ package lagoon
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
@@ -184,7 +185,52 @@ func convertValue(src reflect.Value, destType reflect.Type) (reflect.Value, erro
|
||||
if src.Type().ConvertibleTo(destType) {
|
||||
return src.Convert(destType), nil
|
||||
}
|
||||
return reflect.Value{}, fmt.Errorf("cannot assign %s to %s", src.Type(), destType)
|
||||
assignErr := fmt.Errorf("cannot assign %s to %s", src.Type(), destType)
|
||||
if out, handled, err := convertText(src, destType); handled {
|
||||
if err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("%w: %w", assignErr, err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
return reflect.Value{}, assignErr
|
||||
}
|
||||
|
||||
var (
|
||||
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
scannerType = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
|
||||
dateType = reflect.TypeOf(Date{})
|
||||
timeOfDayType = reflect.TypeOf(TimeOfDay{})
|
||||
)
|
||||
|
||||
// convertText fills a destination type whose pointer implements
|
||||
// encoding.TextUnmarshaler (time.Time, Date, TimeOfDay) from a string or
|
||||
// []byte source, so a JSON date string fills a date column. It runs only
|
||||
// after every other conversion failed, and it skips types that also
|
||||
// implement sql.Scanner (other than Date and TimeOfDay), which setField
|
||||
// already fills through Scan, so nothing that filled before changes path.
|
||||
// handled is false when the fallback does not apply.
|
||||
func convertText(src reflect.Value, destType reflect.Type) (reflect.Value, bool, error) {
|
||||
var text []byte
|
||||
switch v := src.Interface().(type) {
|
||||
case string:
|
||||
text = []byte(v)
|
||||
case []byte:
|
||||
text = v
|
||||
default:
|
||||
return reflect.Value{}, false, nil
|
||||
}
|
||||
ptrType := reflect.PointerTo(destType)
|
||||
if !ptrType.Implements(textUnmarshalerType) {
|
||||
return reflect.Value{}, false, nil
|
||||
}
|
||||
if ptrType.Implements(scannerType) && destType != dateType && destType != timeOfDayType {
|
||||
return reflect.Value{}, false, nil
|
||||
}
|
||||
ptr := reflect.New(destType)
|
||||
if err := ptr.Interface().(encoding.TextUnmarshaler).UnmarshalText(text); err != nil {
|
||||
return reflect.Value{}, true, err
|
||||
}
|
||||
return ptr.Elem(), true, nil
|
||||
}
|
||||
|
||||
// convertNumber parses a json.Number, which a request decoder using
|
||||
|
||||
Reference in New Issue
Block a user