Files
upstream-casbin/transaction_conflict.go
upstream-mirror d8971e16fb
Some checks failed
Build / test (1.21) (push) Successful in 2m21s
Build / benchmark (1.21) (push) Failing after 2m28s
golangci-lint / golangci (push) Successful in 19m40s
snapshot of https://github.com/casbin/casbin.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:46:19 +05:00

138 lines
3.9 KiB
Go

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package casbin
import (
"fmt"
"github.com/casbin/casbin/v3/model"
"github.com/casbin/casbin/v3/persist"
)
// ConflictError represents a transaction conflict error.
type ConflictError struct {
Operation persist.PolicyOperation
Reason string
}
func (e *ConflictError) Error() string {
return fmt.Sprintf("transaction conflict: %s for operation %v", e.Reason, e.Operation)
}
// ConflictDetector detects conflicts between transaction operations and current model state.
type ConflictDetector struct {
baseModel model.Model // Model snapshot when transaction started
currentModel model.Model // Current model state
operations []persist.PolicyOperation // Operations to be applied
}
// NewConflictDetector creates a new conflict detector instance.
func NewConflictDetector(baseModel, currentModel model.Model, operations []persist.PolicyOperation) *ConflictDetector {
return &ConflictDetector{
baseModel: baseModel,
currentModel: currentModel,
operations: operations,
}
}
// DetectConflicts checks for conflicts between the transaction operations and current model state.
// Returns nil if no conflicts are found, otherwise returns a ConflictError describing the conflict.
func (cd *ConflictDetector) DetectConflicts() error {
for _, op := range cd.operations {
var err error
switch op.Type {
case persist.OperationAdd:
// Add operations never conflict
continue
case persist.OperationRemove:
err = cd.detectRemoveConflict(op)
case persist.OperationUpdate:
err = cd.detectUpdateConflict(op)
}
if err != nil {
return err
}
}
return nil
}
// detectRemoveConflict checks for conflicts in remove operations.
func (cd *ConflictDetector) detectRemoveConflict(op persist.PolicyOperation) error {
for _, rule := range op.Rules {
// Check if policy existed in base model
baseHasPolicy, err := cd.baseModel.HasPolicy(op.Section, op.PolicyType, rule)
if err != nil {
return err
}
if !baseHasPolicy {
continue // Policy didn't exist when transaction started
}
// Check if policy still exists in current model
currentHasPolicy, err := cd.currentModel.HasPolicy(op.Section, op.PolicyType, rule)
if err != nil {
return err
}
if !currentHasPolicy {
return &ConflictError{
Operation: op,
Reason: "policy has been removed by another transaction",
}
}
}
return nil
}
// detectUpdateConflict checks for conflicts in update operations.
func (cd *ConflictDetector) detectUpdateConflict(op persist.PolicyOperation) error {
for i, oldRule := range op.OldRules {
if i >= len(op.Rules) {
break
}
newRule := op.Rules[i]
// Check if old policy still exists
oldExists, err := cd.currentModel.HasPolicy(op.Section, op.PolicyType, oldRule)
if err != nil {
return err
}
if !oldExists {
return &ConflictError{
Operation: op,
Reason: "policy to be updated no longer exists",
}
}
// Check if new policy already exists
newExists, err := cd.currentModel.HasPolicy(op.Section, op.PolicyType, newRule)
if err != nil {
return err
}
if newExists {
return &ConflictError{
Operation: op,
Reason: "target policy already exists",
}
}
}
return nil
}