package wire import ( "bytes" "encoding/json" "net/http" "time" ) // WriteJSON encodes v as JSON with HTML escaping disabled and no trailing // newline, matching the PHP json_encode body shape used by house handlers. func WriteJSON(w http.ResponseWriter, status int, v any) { var buf bytes.Buffer enc := json.NewEncoder(&buf) enc.SetEscapeHTML(false) if err := enc.Encode(v); err != nil { WriteOpaque500(w) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _, _ = w.Write(bytes.TrimSuffix(buf.Bytes(), []byte("\n"))) } // WriteOpaque500 writes the house opaque 500 body. func WriteOpaque500(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte(`{"error":true,"message":"Internal server error"}`)) } // Time marshals as Carbon's +00:00 form (2006-01-02T15:04:05+00:00), never // Go's default "Z". Unmarshal accepts both +00:00 and Z for read paths. type Time struct{ time.Time } const ( carbonLayout = "2006-01-02T15:04:05-07:00" rfc3339Z = time.RFC3339 ) func (t Time) MarshalJSON() ([]byte, error) { s := t.UTC().Format("2006-01-02T15:04:05") + "+00:00" return []byte(`"` + s + `"`), nil } func (t *Time) UnmarshalJSON(b []byte) error { if t == nil { return nil } if string(b) == "null" { t.Time = time.Time{} return nil } var s string if err := json.Unmarshal(b, &s); err != nil { return err } var err error for _, layout := range []string{carbonLayout, rfc3339Z} { var parsed time.Time parsed, err = time.Parse(layout, s) if err == nil { t.Time = parsed return nil } } return err } // TriBool keeps PHP's nullable-boolean tri-state: Valid=false marshals null. type TriBool struct{ Valid, Value bool } func (b TriBool) MarshalJSON() ([]byte, error) { if !b.Valid { return []byte("null"), nil } if b.Value { return []byte("true"), nil } return []byte("false"), nil } // Slice returns s unchanged if non-nil, else a non-nil empty slice that // marshals to []. func Slice[T any](s []T) []T { if s == nil { return []T{} } return s }