fix: strict ClockTime parsing and ISODuration SQL
Validate constructors, strict HH:MM:SS parse, UTC ClockTimeFromTime, ISODuration Value/Scan, Compare and TextMarshaler, codec.go split. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
package clocktime
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
func marshalJSONString(s string) ([]byte, error) {
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
func unmarshalJSONString(data []byte) (string, error) {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// MarshalJSON encodes ClockTime as a JSON string HH:MM:SS.
|
||||
func (t ClockTime) MarshalJSON() ([]byte, error) {
|
||||
return marshalJSONString(t.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a JSON string into ClockTime.
|
||||
func (t *ClockTime) UnmarshalJSON(data []byte) error {
|
||||
if string(data) == "null" {
|
||||
*t = ClockTime{}
|
||||
return nil
|
||||
}
|
||||
s, err := unmarshalJSONString(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parsed, err := ParseClockTime(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t = parsed
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (t ClockTime) MarshalText() ([]byte, error) {
|
||||
return []byte(t.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (t *ClockTime) UnmarshalText(text []byte) error {
|
||||
parsed, err := ParseClockTime(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t = parsed
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalGQL implements gqlgen's Marshaler interface.
|
||||
func (t ClockTime) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, "\""+t.String()+"\"")
|
||||
}
|
||||
|
||||
// UnmarshalGQL implements gqlgen's Unmarshaler interface.
|
||||
func (t *ClockTime) UnmarshalGQL(value any) error {
|
||||
if value == nil {
|
||||
*t = ClockTime{}
|
||||
return nil
|
||||
}
|
||||
str, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("ClockTime must be a string, got %T", value)
|
||||
}
|
||||
parsed, err := ParseClockTime(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t = parsed
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements driver.Valuer for SQL TIME columns as HH:MM:SS bytes.
|
||||
func (t ClockTime) Value() (driver.Value, error) {
|
||||
return []byte(t.String()), nil
|
||||
}
|
||||
|
||||
// Scan implements sql.Scanner for time.Time, []byte, and string.
|
||||
func (t *ClockTime) Scan(value any) error {
|
||||
if value == nil {
|
||||
*t = ClockTime{}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case time.Time:
|
||||
*t = ClockTimeFromTime(v)
|
||||
case []byte:
|
||||
parsed, err := ParseClockTime(string(v))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse ClockTime: %w", err)
|
||||
}
|
||||
*t = parsed
|
||||
case string:
|
||||
parsed, err := ParseClockTime(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse ClockTime: %w", err)
|
||||
}
|
||||
*t = parsed
|
||||
default:
|
||||
return fmt.Errorf("ClockTime.Scan: unsupported type %T", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON encodes ISODuration as a JSON ISO 8601 string.
|
||||
func (d ISODuration) MarshalJSON() ([]byte, error) {
|
||||
return marshalJSONString(d.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a JSON ISO 8601 duration string.
|
||||
func (d *ISODuration) UnmarshalJSON(data []byte) error {
|
||||
if string(data) == "null" {
|
||||
*d = ISODuration(0)
|
||||
return nil
|
||||
}
|
||||
s, err := unmarshalJSONString(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parsed, err := ParseISODuration(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = parsed
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (d ISODuration) MarshalText() ([]byte, error) {
|
||||
return []byte(d.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (d *ISODuration) UnmarshalText(text []byte) error {
|
||||
parsed, err := ParseISODuration(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = parsed
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalGQL implements gqlgen's Marshaler interface.
|
||||
func (d ISODuration) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, "\""+d.String()+"\"")
|
||||
}
|
||||
|
||||
// UnmarshalGQL implements gqlgen's Unmarshaler interface.
|
||||
func (d *ISODuration) UnmarshalGQL(value any) error {
|
||||
if value == nil {
|
||||
*d = ISODuration(0)
|
||||
return nil
|
||||
}
|
||||
s, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("ISODuration must be a string, got %T", value)
|
||||
}
|
||||
parsed, err := ParseISODuration(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = parsed
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value stores ISODuration as nanoseconds in the database.
|
||||
func (d ISODuration) Value() (driver.Value, error) {
|
||||
return int64(d), nil
|
||||
}
|
||||
|
||||
// Scan reads ISODuration from int64 nanoseconds or an ISO 8601 string.
|
||||
func (d *ISODuration) Scan(value any) error {
|
||||
if value == nil {
|
||||
*d = ISODuration(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case int64:
|
||||
*d = ISODuration(v)
|
||||
case []byte:
|
||||
parsed, err := ParseISODuration(string(v))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = parsed
|
||||
case string:
|
||||
parsed, err := ParseISODuration(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = parsed
|
||||
default:
|
||||
return fmt.Errorf("ISODuration.Scan: unsupported type %T", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user