diff --git a/pkg/common/recursive_crud.go b/pkg/common/recursive_crud.go index 211c93a..04b1290 100644 --- a/pkg/common/recursive_crud.go +++ b/pkg/common/recursive_crud.go @@ -113,7 +113,7 @@ func (p *NestedCUDProcessor) ProcessNestedCUD( // Process based on operation switch strings.ToLower(operation) { - case "insert", "create": + case "insert", "create", "add": // Only perform insert if we have data to insert if hasData { id, err := p.processInsert(ctx, regularData, tableName) @@ -141,7 +141,7 @@ func (p *NestedCUDProcessor) ProcessNestedCUD( logger.Debug("Skipping insert for %s - no data columns besides _request", tableName) } - case "update", "change": + case "update", "change", "modify": // Only perform update if we have data to update if reflection.IsEmptyValue(data[pkName]) { logger.Warn("Skipping update for %s - no primary key", tableName) @@ -174,7 +174,7 @@ func (p *NestedCUDProcessor) ProcessNestedCUD( result.ID = data[pkName] } - case "delete": + case "delete", "remove": if reflection.IsEmptyValue(data[pkName]) { logger.Warn("Skipping delete for %s - no primary key", tableName) return result, nil diff --git a/pkg/restheadspec/handler.go b/pkg/restheadspec/handler.go index 22c22e7..bf99d67 100644 --- a/pkg/restheadspec/handler.go +++ b/pkg/restheadspec/handler.go @@ -2043,7 +2043,10 @@ func (h *Handler) processChildRelationsForField( // Process based on relation type and data structure switch v := relationValue.(type) { case map[string]interface{}: - // Single related object - add parent ID to foreign key field + if !isValidNestedRequest(v) { + logger.Debug("Skipping single relation %s - missing or invalid _request value", relationName) + return nil + } // IMPORTANT: In recursive relationships, don't overwrite the primary key if parentID != nil && foreignKeyFieldName != "" && foreignKeyFieldName != childPKFieldName { v[foreignKeyFieldName] = parentID @@ -2060,7 +2063,10 @@ func (h *Handler) processChildRelationsForField( // Multiple related objects for i, item := range v { if itemMap, ok := item.(map[string]interface{}); ok { - // Add parent ID to foreign key field + if !isValidNestedRequest(itemMap) { + logger.Debug("Skipping relation array[%d] %s - missing or invalid _request value", i, relationName) + continue + } // IMPORTANT: In recursive relationships, don't overwrite the primary key if parentID != nil && foreignKeyFieldName != "" && foreignKeyFieldName != childPKFieldName { itemMap[foreignKeyFieldName] = parentID @@ -2078,7 +2084,10 @@ func (h *Handler) processChildRelationsForField( case []map[string]interface{}: // Multiple related objects (typed slice) for i, itemMap := range v { - // Add parent ID to foreign key field + if !isValidNestedRequest(itemMap) { + logger.Debug("Skipping relation typed array[%d] %s - missing or invalid _request value", i, relationName) + continue + } // IMPORTANT: In recursive relationships, don't overwrite the primary key if parentID != nil && foreignKeyFieldName != "" && foreignKeyFieldName != childPKFieldName { itemMap[foreignKeyFieldName] = parentID @@ -2099,6 +2108,24 @@ func (h *Handler) processChildRelationsForField( return nil } +// isValidNestedRequest returns true only when the item carries a _request key +// whose value is one of the recognised mutation verbs. +func isValidNestedRequest(item map[string]interface{}) bool { + raw, ok := item["_request"] + if !ok { + return false + } + s, ok := raw.(string) + if !ok { + return false + } + switch strings.ToLower(strings.TrimSpace(s)) { + case "insert", "create", "add", "change", "update", "modify", "delete", "remove": + return true + } + return false +} + // getTableNameForRelatedModel gets the table name for a related model. // If the model's TableName() is schema-qualified (e.g. "public.users") the // separator is adjusted for the active driver: underscore for SQLite, dot otherwise.