fix(12.2): close code-review blockers on uploads, JSON caps, and pivot fill

Keep form save behind in-flight uploads, make retries idempotent via X-Upload-Id, cap remaining JSON bodies, and surface pending pivot type errors instead of zeroing them.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-10-02 23:10:48 +02:00
parent 6bfc0faa8a
commit 516f9c9025
22 changed files with 533 additions and 96 deletions

View File

@@ -466,7 +466,7 @@ func (s *service) settingsGet(w http.ResponseWriter, r *http.Request) {
func (s *service) settingsPut(w http.ResponseWriter, r *http.Request) {
s.protectSetting(w, r, func(setting *CompiledSetting) {
body, err := decodeObject(r)
body, err := s.decodeCappedObject(w, r)
if err != nil {
writeCRUDError(w, err)
return
@@ -587,7 +587,7 @@ func (s *service) relationMutation(w http.ResponseWriter, r *http.Request, link
writeCRUDError(w, err)
return
}
in, err := decodeRelationMutation(r)
in, err := s.decodeCappedRelationMutation(w, r)
if err != nil {
writeCRUDError(w, err)
return
@@ -611,11 +611,19 @@ func (s *service) relationMutation(w http.ResponseWriter, r *http.Request, link
}
func decodeRelationMutation(r *http.Request) (RelationMutationInput, error) {
dec := json.NewDecoder(r.Body)
return decodeRelationMutationBody(r.Body)
}
func decodeRelationMutationBody(body io.Reader) (RelationMutationInput, error) {
dec := json.NewDecoder(body)
dec.UseNumber()
dec.DisallowUnknownFields()
var in RelationMutationInput
if err := dec.Decode(&in); err != nil {
var tooBig *http.MaxBytesError
if errors.As(err, &tooBig) {
return RelationMutationInput{}, err
}
return RelationMutationInput{}, relationInvalid("body", "The request body is invalid.")
}
var trailing any
@@ -625,6 +633,10 @@ func decodeRelationMutation(r *http.Request) (RelationMutationInput, error) {
return in, nil
}
func (s *service) decodeCappedRelationMutation(w http.ResponseWriter, r *http.Request) (RelationMutationInput, error) {
return decodeRelationMutationBody(http.MaxBytesReader(w, r.Body, s.jsonCap()))
}
func (s *service) relations() (RelationService, error) {
db, err := s.db()
if err != nil {
@@ -758,7 +770,7 @@ func (s *service) create(w http.ResponseWriter, r *http.Request) {
writeCRUDError(w, err)
return
}
body, err := decodeObject(r)
body, err := s.decodeCappedObject(w, r)
if err != nil {
writeCRUDError(w, err)
return
@@ -792,7 +804,7 @@ func (s *service) update(w http.ResponseWriter, r *http.Request) {
writeCRUDError(w, err)
return
}
body, err := decodeObject(r)
body, err := s.decodeCappedObject(w, r)
if err != nil {
writeCRUDError(w, err)
return
@@ -816,7 +828,7 @@ func (s *service) bulkDelete(w http.ResponseWriter, r *http.Request) {
if !s.operationDeclared(w, r, cc, "bulk-delete") {
return
}
in, err := decodeBulk(r)
in, err := s.decodeCappedBulk(w, r)
if err != nil {
writeCRUDError(w, err)
return
@@ -836,15 +848,27 @@ func (s *service) bulkDelete(w http.ResponseWriter, r *http.Request) {
}
func decodeBulk(r *http.Request) (BulkDeleteInput, error) {
dec := json.NewDecoder(r.Body)
return decodeBulkBody(r.Body)
}
func decodeBulkBody(body io.Reader) (BulkDeleteInput, error) {
dec := json.NewDecoder(body)
dec.UseNumber()
var in BulkDeleteInput
if err := dec.Decode(&in); err != nil {
var tooBig *http.MaxBytesError
if errors.As(err, &tooBig) {
return BulkDeleteInput{}, err
}
return BulkDeleteInput{}, &ValidationError{Details: map[string]any{"body": []string{"The request body is invalid."}}}
}
return in, nil
}
func (s *service) decodeCappedBulk(w http.ResponseWriter, r *http.Request) (BulkDeleteInput, error) {
return decodeBulkBody(http.MaxBytesReader(w, r.Body, s.jsonCap()))
}
func (s *service) deleteRecord(w http.ResponseWriter, r *http.Request) {
s.protect(w, r, func(cc *CompiledController) {
if !s.operationDeclared(w, r, cc, "delete") {