package domain import ( "errors" "fmt" "sort" ) // ErrReorderInvalidIDSet means ordered_ids is not an exact permutation of the current entities in scope. var ErrReorderInvalidIDSet = errors.New("ordered_ids must list every id in this scope exactly once, with no duplicates") // ReorderIDsRequest is the body for batch reorder endpoints (drag-and-drop UI). // Send "ordered_ids": [] in display order. Must include every id in that scope (use GET list) when there is at least one entity. type ReorderIDsRequest struct { OrderedIDs []int64 `json:"ordered_ids"` } // ValidateReorderPermutation checks that ordered contains the same multiset of ids as expected (new order vs current scope). func ValidateReorderPermutation(ordered, expected []int64) error { if len(expected) == 0 { if len(ordered) == 0 { return nil } return fmt.Errorf("%w: no entities exist in this scope", ErrReorderInvalidIDSet) } if len(ordered) != len(expected) { return fmt.Errorf("%w: want %d ids, got %d", ErrReorderInvalidIDSet, len(expected), len(ordered)) } seen := make(map[int64]struct{}, len(ordered)) for _, id := range ordered { if _, dup := seen[id]; dup { return fmt.Errorf("%w: duplicate id %d", ErrReorderInvalidIDSet, id) } seen[id] = struct{}{} } a := append([]int64(nil), expected...) b := append([]int64(nil), ordered...) sort.Slice(a, func(i, j int) bool { return a[i] < a[j] }) sort.Slice(b, func(i, j int) bool { return b[i] < b[j] }) for i := range a { if a[i] != b[i] { return fmt.Errorf("%w: id set does not match current scope", ErrReorderInvalidIDSet) } } return nil }