Ein Konto ohne Kalender bekam auf GET /api/v1/calendars die Antwort
`null`, und die Ansicht brach ab:
for (const cal of calendars) -> "e is not iterable"
Nicht der Kalender ist schuld, sondern eine Eigenheit, die jeden
Listenendpunkt betrifft: In Go ist eine Liste ohne Eintraege oft nil,
und encoding/json schreibt dafuer null statt []. Jede Store-Funktion,
die mit `var out []T` anfaengt, liefert das bei leerem Ergebnis --
Favoriten, Papierkorb, Versionen, Freigaben, Etiketten. Der Kalender war
nur der, der daran zerbricht.
Deshalb die Regel dort, wo alle Antworten durchlaufen: response.JSON
macht aus einer nil-Liste eine leere. Einzelne Objekte und nil bleiben
unberuehrt. In den Stores waere es die falsche Stelle -- es gibt
Dutzende Listenendpunkte, und der naechste faengt wieder mit
`var out []T` an. Eine Regel, an die man sich erinnern muss, ist keine.
Dazu zwei Dinge, die derselbe Fall ans Licht brachte:
- Der Web-Weg legte, anders als CalDAV, keinen Standardkalender an.
Ein neues Konto stand vor einer leeren Ansicht und musste erraten,
dass es selbst einen anlegen soll -- waehrend ein CalDAV-Client, der
sich vorher verbunden hatte, bereits einen sah, den die Oberflaeche
nicht zeigte.
- Die Ansicht faengt eine unerwartete Antwort jetzt selbst ab (?? []).
Der Server schickt kein null mehr; eine Seite sollte daran trotzdem
nicht zerbrechen.
Drei Tests in pkg/response: leere Liste wird [], befuellte bleibt
unveraendert, Objekte und nil bleiben, was sie sind.
Zur Korrektur meiner frueheren Erklaerung: Die fehlenden Cache-Header
waren ein echtes Problem, aber nicht die Ursache dieses Fehlers. Der
gemeldete Bundle-Name stimmte diesmal mit dem ausgelieferten ueberein --
daran war zu sehen, dass es kein alter Stand sein konnte.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
53 lines
1.5 KiB
Go
53 lines
1.5 KiB
Go
package response
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"reflect"
|
||
)
|
||
|
||
type envelope struct {
|
||
Data any `json:"data,omitempty"`
|
||
Error string `json:"error,omitempty"`
|
||
}
|
||
|
||
func JSON(w http.ResponseWriter, status int, data any) {
|
||
w.Header().Set("Content-Type", "application/json")
|
||
w.WriteHeader(status)
|
||
json.NewEncoder(w).Encode(envelope{Data: leereListeStattNull(data)})
|
||
}
|
||
|
||
// leereListeStattNull macht aus einer nicht belegten Liste eine leere.
|
||
//
|
||
// In Go ist eine Liste ohne Einträge oft nil, und `encoding/json` schreibt
|
||
// dafür `null` statt `[]`. Jede Store-Funktion, die ihre Liste mit
|
||
// `var out []T` beginnt, liefert das bei leerem Ergebnis – und jede
|
||
// Oberfläche, die darüber läuft, bricht dann ab.
|
||
//
|
||
// Genau so ist die Kalenderansicht bei einem Konto ohne Kalender
|
||
// weiß geblieben: `for (const cal of calendars)` über `null`.
|
||
//
|
||
// Der Ort dafür ist hier und nicht in den Stores: Es gibt Dutzende
|
||
// Listenendpunkte, und der nächste wird wieder mit `var out []T` anfangen.
|
||
// Eine Regel, an die man sich erinnern muss, ist keine.
|
||
func leereListeStattNull(data any) any {
|
||
if data == nil {
|
||
return data
|
||
}
|
||
v := reflect.ValueOf(data)
|
||
if v.Kind() == reflect.Slice && v.IsNil() {
|
||
return reflect.MakeSlice(v.Type(), 0, 0).Interface()
|
||
}
|
||
return data
|
||
}
|
||
|
||
func Error(w http.ResponseWriter, status int, msg string) {
|
||
w.Header().Set("Content-Type", "application/json")
|
||
w.WriteHeader(status)
|
||
json.NewEncoder(w).Encode(envelope{Error: msg})
|
||
}
|
||
|
||
func NoContent(w http.ResponseWriter) {
|
||
w.WriteHeader(http.StatusNoContent)
|
||
}
|