fix(handler): validate nested request structure for relations

* added checks for valid _request values in single and multiple relations
* introduced isValidNestedRequest function to encapsulate validation logic
fix(crud): expand operation handling for nested CUD
* added "add" to insert operations and "modify" to update operations
* included "remove" in delete operations
This commit is contained in:
Hein
2026-06-08 09:02:29 +02:00
parent 29449c93d5
commit a87cd18b1b
2 changed files with 33 additions and 6 deletions
+3 -3
View File
@@ -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
+30 -3
View File
@@ -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.