Skip to content

Commit 8e7a510

Browse files
committed
fix(adapter): correct removePolicies semantics and filtering; align MemoryAdapter load/save with model
- Make removePolicies atomic: return true only if all rules exist; otherwise do not remove any - Fix inverted existence check and remove early return - Mirror semantics in DefaultModel.removePolicies - MemoryAdapter.savePolicy: write explicit section ('p'/'g') instead of iterating ptype characters - MemoryAdapter.loadPolicy/loadFilteredPolicy: drop [sec, ptype] headers; append only rule fields - Fix loadFilteredPolicy indexing to match dropped headers - Tests: re-enable async removePolicies test; update expectations to no-ptype rules
1 parent 606c3af commit 8e7a510

File tree

4 files changed

+232
-55
lines changed

4 files changed

+232
-55
lines changed

Sources/Casbin/Adapter/MemoryAdapter.swift

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ extension MemoryAdapter: Adapter {
3131
for line in policy {
3232
let sec = line[0]
3333
let ptype = line[1]
34-
let rule = Array(line[1...])
34+
// Model policies should NOT include ptype; drop sec and ptype headers
35+
let rule = Array(line.dropFirst(2))
3536
if let ast = m.getModel()[sec]?[ptype] {
3637
ast.policy.append(rule)
3738
}
@@ -44,7 +45,7 @@ extension MemoryAdapter: Adapter {
4445
for line in policy {
4546
let sec = line[0]
4647
let ptype = line[1]
47-
let rule = Array(line[1...])
48+
let rule = Array(line.dropFirst(2))
4849
if let ast = m.getModel()[sec]?[ptype] {
4950
ast.policy.append(rule)
5051
}
@@ -55,18 +56,18 @@ extension MemoryAdapter: Adapter {
5556
for line in policy {
5657
let sec = line[0]
5758
let ptype = line[1]
58-
let rule = Array(line[1...])
59+
let rule = Array(line.dropFirst(2))
5960
var isFiltered = false
6061
if sec == "p" {
6162
for (i,r) in f.p.enumerated() {
62-
if !r.isEmpty && r != rule[i+1] {
63+
if !r.isEmpty && r != rule[i] {
6364
isFiltered = true
6465
}
6566
}
6667
}
6768
if sec == "g" {
6869
for (i,r) in f.g.enumerated() {
69-
if !r.isEmpty && r != rule[i+1] {
70+
if !r.isEmpty && r != rule[i] {
7071
isFiltered = true
7172
}
7273
}
@@ -87,18 +88,18 @@ extension MemoryAdapter: Adapter {
8788
for line in policy {
8889
let sec = line[0]
8990
let ptype = line[1]
90-
let rule = Array(line[1...])
91+
let rule = Array(line.dropFirst(2))
9192
var isFiltered = false
9293
if sec == "p" {
9394
for (i,r) in f.p.enumerated() {
94-
if !r.isEmpty && r != rule[i+1] {
95+
if !r.isEmpty && r != rule[i] {
9596
isFiltered = true
9697
}
9798
}
9899
}
99100
if sec == "g" {
100101
for (i,r) in f.g.enumerated() {
101-
if !r.isEmpty && r != rule[i+1] {
102+
if !r.isEmpty && r != rule[i] {
102103
isFiltered = true
103104
}
104105
}
@@ -117,25 +118,21 @@ extension MemoryAdapter: Adapter {
117118
self.policy = []
118119
if let astMap = m.getModel()["p"] {
119120
for (ptype,ast) in astMap {
120-
ptype.forEach { sec in
121-
for policy in ast.policy {
122-
var rule = policy
123-
rule.insert(ptype, at: 0)
124-
rule.insert(String(sec), at: 0)
125-
self.policy.insert(rule)
126-
}
121+
for policy in ast.policy {
122+
var rule = policy
123+
rule.insert(ptype, at: 0)
124+
rule.insert("p", at: 0)
125+
self.policy.insert(rule)
127126
}
128127
}
129128
}
130129
if let astMap = m.getModel()["g"] {
131130
for (ptype,ast) in astMap {
132-
ptype.forEach { sec in
133-
for policy in ast.policy {
134-
var rule = policy
135-
rule.insert(ptype, at: 0)
136-
rule.insert(String(sec), at: 0)
137-
self.policy.insert(rule)
138-
}
131+
for policy in ast.policy {
132+
var rule = policy
133+
rule.insert(ptype, at: 0)
134+
rule.insert("g", at: 0)
135+
self.policy.insert(rule)
139136
}
140137
}
141138
}
@@ -147,25 +144,21 @@ extension MemoryAdapter: Adapter {
147144
self.policy = []
148145
if let astMap = m.getModel()["p"] {
149146
for (ptype,ast) in astMap {
150-
ptype.forEach { sec in
151-
for policy in ast.policy {
152-
var rule = policy
153-
rule.insert(ptype, at: 0)
154-
rule.insert(String(sec), at: 0)
155-
self.policy.insert(rule)
156-
}
147+
for policy in ast.policy {
148+
var rule = policy
149+
rule.insert(ptype, at: 0)
150+
rule.insert("p", at: 0)
151+
self.policy.insert(rule)
157152
}
158153
}
159154
}
160155
if let astMap = m.getModel()["g"] {
161156
for (ptype,ast) in astMap {
162-
ptype.forEach { sec in
163-
for policy in ast.policy {
164-
var rule = policy
165-
rule.insert(ptype, at: 0)
166-
rule.insert(String(sec), at: 0)
167-
self.policy.insert(rule)
168-
}
157+
for policy in ast.policy {
158+
var rule = policy
159+
rule.insert(ptype, at: 0)
160+
rule.insert("g", at: 0)
161+
self.policy.insert(rule)
169162
}
170163
}
171164
}
@@ -258,14 +251,17 @@ extension MemoryAdapter: Adapter {
258251
rule.insert(sec, at: 0)
259252
return rule
260253
}
254+
// Atomic semantics: if any rule doesn't exist, do not remove any and return false.
261255
for rule in rules {
262-
if policy.contains(rule) {
256+
if !policy.contains(rule) {
263257
allRemoved = false
264-
return eventloop.makeSucceededFuture(allRemoved)
258+
break
265259
}
266260
}
267-
for rule in rules {
268-
self.policy.remove(rule)
261+
if allRemoved {
262+
for rule in rules {
263+
_ = self.policy.remove(rule)
264+
}
269265
}
270266
return eventloop.makeSucceededFuture(allRemoved)
271267
}
@@ -279,14 +275,17 @@ extension MemoryAdapter: Adapter {
279275
rule.insert(sec, at: 0)
280276
return rule
281277
}
278+
// Atomic semantics
282279
for rule in rules {
283-
if policy.contains(rule) {
280+
if !policy.contains(rule) {
284281
allRemoved = false
285-
return allRemoved
282+
break
286283
}
287284
}
288-
for rule in rules {
289-
self.policy.remove(rule)
285+
if allRemoved {
286+
for rule in rules {
287+
_ = self.policy.remove(rule)
288+
}
290289
}
291290
return allRemoved
292291
}

Sources/Casbin/Model/DefaultModel.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,17 @@ extension DefaultModel:Model {
223223
public func removePolicies(sec:String,ptype:String,rules:[[String]]) -> Bool {
224224
var allRemoved = true
225225
if let ast = model[sec]?[ptype] {
226+
// Atomic semantics: if any rule is missing, do nothing and return false
226227
for rule in rules {
227-
if ast.policy.contains(rule) {
228+
if !ast.policy.contains(rule) {
228229
allRemoved = false
229-
return allRemoved
230+
break
230231
}
231232
}
232-
for rule in rules {
233-
ast.policy.removeAll { $0 == rule }
233+
if allRemoved {
234+
for rule in rules {
235+
ast.policy.removeAll { $0 == rule }
236+
}
234237
}
235238
}
236239
return allRemoved

Tests/CasbinTests/MemoryAdapterAsyncTests.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ struct MemoryAdapterAsyncTests {
3636
Issue.record("Policy not found in model")
3737
return
3838
}
39-
// Note: policy array includes ptype as first element
40-
#expect(ast.policy.contains(["p", "alice", "data1", "read"]))
39+
// Model policies do NOT include ptype; only the rule fields
40+
#expect(ast.policy.contains(["alice", "data1", "read"]))
4141
}
4242

4343
@Test("async addPolicies and savePolicy")
@@ -97,7 +97,7 @@ struct MemoryAdapterAsyncTests {
9797
#expect(ast.policy.isEmpty)
9898
}
9999

100-
@Test("async removePolicies", .disabled("Pre-existing bug in removePolicies logic - checks if policy.contains() instead of !policy.contains()"))
100+
@Test("async removePolicies")
101101
func testAsyncRemovePolicies() async throws {
102102
let adapter = makeAdapter()
103103
let model = makeModel()
@@ -113,9 +113,9 @@ struct MemoryAdapterAsyncTests {
113113
// Note: removePolicies has a bug - it checks if policies exist and returns false
114114
// This is pre-existing behavior in the original implementation
115115
let removed = try await adapter.removePolicies(sec: "p", ptype: "p", rules: rules)
116+
#expect(removed == true)
116117

117-
// Due to the bug, removed will be false even though policies were removed
118-
// Let's just verify the policies are actually gone
118+
// Verify the policies are actually gone
119119
try await adapter.loadPolicy(m: model)
120120
guard let ast = model.getModel()["p"]?["p"] else {
121121
// No policies - this is expected
@@ -172,8 +172,7 @@ struct MemoryAdapterAsyncTests {
172172
return
173173
}
174174
#expect(ast.policy.count == 1)
175-
// Note: policy array includes ptype as first element
176-
#expect(ast.policy.contains(["p", "bob", "data1", "read"]))
175+
#expect(ast.policy.contains(["bob", "data1", "read"]))
177176
}
178177

179178
@Test("async loadFilteredPolicy")

0 commit comments

Comments
 (0)