diff --git a/internal/forms/testdata/uuid1.http b/internal/forms/testdata/uuid1.http new file mode 100644 index 0000000..90a43a9 --- /dev/null +++ b/internal/forms/testdata/uuid1.http @@ -0,0 +1,22 @@ +POST /api/incident/8ff93b85-c604-11ef-a555-00e04c0122ba/calls HTTP/1.1 +Connection: Upgrade, HTTP2-Settings +Content-Length: 403 +Host: xenon:3050 +HTTP2-Settings: AAEAAEAAAAIAAAAAAAMAAAAAAAQBAAAAAAUAAEAAAAYABgAA +Upgrade: h2c +Content-Type: multipart/form-data; boundary=--sdrtrunk-sdrtrunk-sdrtrunk +User-Agent: sdrtrunk + +----sdrtrunk-sdrtrunk-sdrtrunk +Content-Disposition: form-data; name="add" + +[f25ef14b-c5f6-11ef-a555-00e04c0122ba,f25ef14b-c5f6-11ef-a555-06e04c0122ba] +----sdrtrunk-sdrtrunk-sdrtrunk +Content-Disposition: form-data; name="notes" + +{"this":"note"} +----sdrtrunk-sdrtrunk-sdrtrunk +Content-Disposition: form-data; name="single" + +17cedf8e-c60b-11ef-a555-00e04c0122ba +----sdrtrunk-sdrtrunk-sdrtrunk diff --git a/internal/forms/unmarshal.go b/internal/forms/unmarshal.go index 5e2dd11..0d04857 100644 --- a/internal/forms/unmarshal.go +++ b/internal/forms/unmarshal.go @@ -14,6 +14,7 @@ import ( "dynatron.me/x/stillbox/internal/jsontypes" "github.com/araddon/dateparse" + "github.com/google/uuid" ) func (o *options) parseTime(s string, dpo ...dateparse.ParserOption) (t time.Time, set bool, err error) { @@ -40,6 +41,22 @@ func (o *options) parseTime(s string, dpo ...dateparse.ParserOption) (t time.Tim return } +func (o *options) parseUUID(s string) (u uuid.UUID, set bool, err error) { + if o.acceptBlank && s == "" { + set = false + return + } + + set = true + + u, err = uuid.Parse(s) + if err != nil { + return u, set, fmt.Errorf("parseUUID('%s'): %w", s, err) + } + + return +} + func (o *options) parseBool(s string) (v bool, set bool, err error) { if o.acceptBlank && s == "" { set = false @@ -212,6 +229,26 @@ func (o *options) unmIterFields(r *http.Request, destStruct reflect.Value) error return err } setVal(destFieldVal, set, d) + case uuid.UUID, jsontypes.UUID: + u, set, err := o.parseUUID(ff) + if err != nil { + return err + } + setVal(destFieldVal, set, u) + case jsontypes.UUIDs: + val := strings.Trim(ff, "[]") + if val == "" && o.acceptBlank { + continue + } + vals := strings.Split(val, ",") + ar := make([]jsontypes.UUID, 0, len(vals)) + for _, v := range vals { + i, err := uuid.Parse(v) + if err == nil { + ar = append(ar, jsontypes.UUID(i)) + } + } + destFieldVal.Set(reflect.ValueOf(ar)) case []int: val := strings.Trim(ff, "[]") if val == "" && o.acceptBlank { diff --git a/internal/forms/unmarshal_test.go b/internal/forms/unmarshal_test.go index ae1898f..da2a988 100644 --- a/internal/forms/unmarshal_test.go +++ b/internal/forms/unmarshal_test.go @@ -2,6 +2,7 @@ package forms_test import ( "bufio" + "encoding/json" "errors" "net/http" "os" @@ -19,6 +20,7 @@ import ( "dynatron.me/x/stillbox/pkg/talkgroups/tgstore" "dynatron.me/x/stillbox/pkg/talkgroups/xport" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -66,6 +68,14 @@ type ptrTestJT struct { ScoreEnd jsontypes.Time `form:"scoreEnd"` } +type CallIncidentParams struct { + Add jsontypes.UUIDs `json:"add"` + Notes json.RawMessage `json:"notes"` + + Remove jsontypes.UUIDs `json:"remove"` + Single jsontypes.UUID `json:"single"` +} + var ( UrlEncTest = urlEncTest{ LookbackDays: 7, @@ -140,6 +150,15 @@ var ( }, }, } + + Cap1 = CallIncidentParams{ + Add: jsontypes.UUIDs{ + jsontypes.UUID(uuid.MustParse("f25ef14b-c5f6-11ef-a555-00e04c0122ba")), + jsontypes.UUID(uuid.MustParse("f25ef14b-c5f6-11ef-a555-06e04c0122ba")), + }, + Single: jsontypes.UUID(uuid.MustParse("17cedf8e-c60b-11ef-a555-00e04c0122ba")), + Notes: []byte(`{"this":"note"}`), + } ) func makeRequest(fixture string) *http.Request { @@ -268,6 +287,13 @@ func TestUnmarshal(t *testing.T) { expect: &ExpJob1, opts: []forms.Option{forms.WithAcceptBlank(), forms.WithOmitEmpty()}, }, + { + name: "uuid and json raw message", + r: makeRequest("uuid1.http"), + dest: &CallIncidentParams{}, + expect: &Cap1, + opts: []forms.Option{forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty()}, + }, } for _, tc := range tests { diff --git a/internal/jsontypes/uuid.go b/internal/jsontypes/uuid.go new file mode 100644 index 0000000..9e05191 --- /dev/null +++ b/internal/jsontypes/uuid.go @@ -0,0 +1,35 @@ +package jsontypes + +import ( + "github.com/google/uuid" +) + +type UUID uuid.UUID +type UUIDs []UUID + +func (u *UUIDs) UUIDs() []uuid.UUID { + r := make([]uuid.UUID, 0, len(*u)) + + for _, v := range *u { + r = append(r, v.UUID()) + } + + return r +} + +func (u UUID) UUID() uuid.UUID { + return uuid.UUID(u) +} + +func (u *UUID) MarshalJSON() ([]byte, error) { + return []byte(`"` + u.UUID().String() + `"`), nil +} + +func (u *UUID) UnmarshalJSON(b []byte) error { + id, err := uuid.Parse(string(b[:])) + if err != nil { + return err + } + *u = UUID(id) + return nil +}