Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
40 changes: 40 additions & 0 deletions Sources/Casbin/Config+AsyncAwait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed 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.

import NIO

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension Config {

// Generic bridge from EventLoopFuture to async/await
private static func awaitFuture<T>(_ future: EventLoopFuture<T>) async throws -> T {
try await withCheckedThrowingContinuation { cont in
future.whenComplete { result in
cont.resume(with: result)
}
}
}

/// Load configuration from a file asynchronously
public static func from(file: String, fileIo: NonBlockingFileIO, on eventloop: EventLoop) async throws -> Config {
let future: EventLoopFuture<Config> = Config.from(file: file, fileIo: fileIo, on: eventloop)
return try await awaitFuture(future)
}

/// Load configuration from text asynchronously
public static func from(text: String, on eventloop: EventLoop) async throws -> Config {
let future: EventLoopFuture<Config> = Config.from(text: text, on: eventloop)
return try await awaitFuture(future)
}
}
98 changes: 98 additions & 0 deletions Sources/Casbin/Enforcer+AsyncAwait.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,103 @@ extension Enforcer {
let f: EventLoopFuture<Bool> = self.removeFilteredGroupingPolicy(fieldIndex: fieldIndex, fieldValues: fieldValues)
return try await awaitFuture(f)
}

// MARK: Management (Named Policy)
public func addNamedPolicy(ptype: String, params: [String]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.addNamedPolicy(ptype: ptype, params: params)
return try await awaitFuture(f)
}

public func addNamedPolicies(ptype: String, paramss: [[String]]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.addNamedPolicies(ptype: ptype, paramss: paramss)
return try await awaitFuture(f)
}

public func removeNamedPolicy(ptype: String, params: [String]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.removeNamedPolicy(ptype: ptype, params: params)
return try await awaitFuture(f)
}

public func removeNamedPolicies(ptype: String, paramss: [[String]]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.removeNamedPolicies(ptype: ptype, paramss: paramss)
return try await awaitFuture(f)
}

public func removeFilteredNamedPolicy(ptype: String, fieldIndex: Int, fieldValues: [String]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.removeFilteredNamedPolicy(ptype: ptype, fieldIndex: fieldIndex, fieldValues: fieldValues)
return try await awaitFuture(f)
}

// MARK: Management (Named Grouping)
public func addNamedGroupingPolicy(ptype: String, params: [String]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.addNamedGroupingPolicy(ptype: ptype, params: params)
return try await awaitFuture(f)
}

public func addNamedGroupingPolicies(ptype: String, paramss: [[String]]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.addNamedGroupingPolicies(ptype: ptype, paramss: paramss)
return try await awaitFuture(f)
}

public func removeNamedGroupingPolicy(ptype: String, params: [String]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.removeNamedGroupingPolicy(ptype: ptype, params: params)
return try await awaitFuture(f)
}

public func removeNamedGroupingPolicies(ptype: String, paramss: [[String]]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.removeNamedGroupingPolicies(ptype: ptype, paramss: paramss)
return try await awaitFuture(f)
}

public func removeFilteredNamedGroupingPolicy(ptype: String, fieldIndex: Int, fieldValues: [String]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.removeFilteredNamedGroupingPolicy(ptype: ptype, fieldIndex: fieldIndex, fieldValues: fieldValues)
return try await awaitFuture(f)
}

// MARK: RBAC API
public func addPermission(for user: String, permission: [String]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.addPermission(for: user, permission: permission)
return try await awaitFuture(f)
}

public func addPermissions(for user: String, permissions: [[String]]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.addPermissions(for: user, permissions: permissions)
return try await awaitFuture(f)
}

public func addRole(for user: String, role: String, domain: String?) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.addRole(for: user, role: role, domain: domain)
return try await awaitFuture(f)
}

public func addRoles(for user: String, roles: [String], domain: String?) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.addRoles(for: user, roles: roles, domain: domain)
return try await awaitFuture(f)
}

public func deleteRole(for user: String, role: String, domain: String?) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.deleteRole(for: user, role: role, domain: domain)
return try await awaitFuture(f)
}

public func deleteRoles(for user: String, roles: [String], domain: String?) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.deleteRoles(for: user, roles: roles, domain: domain)
return try await awaitFuture(f)
}

public func deleteUser(name: String) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.deleteUser(name: name)
return try await awaitFuture(f)
}

public func deleteRole(name: String) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.deleteRole(name: name)
return try await awaitFuture(f)
}

public func deletePermission(for user: String, permission: [String]) async throws -> Bool {
let f: EventLoopFuture<Bool> = self.deletePermission(for: user, permission: permission)
return try await awaitFuture(f)
}
}

40 changes: 40 additions & 0 deletions Sources/Casbin/Model/DefaultModel+AsyncAwait.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed 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.

import NIO

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension DefaultModel {

// Generic bridge from EventLoopFuture to async/await
private static func awaitFuture<T>(_ future: EventLoopFuture<T>) async throws -> T {
try await withCheckedThrowingContinuation { cont in
future.whenComplete { result in
cont.resume(with: result)
}
}
}

/// Load model from a file asynchronously
public static func from(file: String, fileIo: NonBlockingFileIO, on eventloop: EventLoop) async throws -> DefaultModel {
let future: EventLoopFuture<DefaultModel> = DefaultModel.from(file: file, fileIo: fileIo, on: eventloop)
return try await awaitFuture(future)
}

/// Load model from text asynchronously
public static func from(text: String, on eventloop: EventLoop) async throws -> DefaultModel {
let future: EventLoopFuture<DefaultModel> = DefaultModel.from(text: text, on: eventloop)
return try await awaitFuture(future)
}
}
159 changes: 159 additions & 0 deletions Tests/CasbinTests/EnforcerManagementAsyncTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import Testing
import Casbin
import NIO

@Suite("Enforcer Management Async/Await API Tests")
struct EnforcerManagementAsyncTests {

private func withEnforcer<R>(_ body: (Enforcer) async throws -> R) async throws -> R {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
try? elg.syncShutdownGracefully()
}

let m = DefaultModel()
_ = m.addDef(sec: "r", key: "r", value: "sub, obj, act")
_ = m.addDef(sec: "p", key: "p", value: "sub, obj, act")
_ = m.addDef(sec: "e", key: "e", value: "some(where (p.eft == allow))")
_ = m.addDef(sec: "m", key: "m", value: "r.sub == p.sub && r.obj == p.obj && r.act == p.act")
_ = m.addDef(sec: "g", key: "g", value: "_, _")

let adapter = MemoryAdapter(on: elg.next())
let e = try Enforcer(m: m, adapter: adapter, .shared(elg))
return try await body(e)
}

@Test("async addNamedPolicy and removeNamedPolicy")
func testAsyncNamedPolicy() async throws {
try await withEnforcer { e in
// Add a named policy
let added = try await e.addNamedPolicy(ptype: "p", params: ["alice", "data1", "read"])
#expect(added == true)

// Verify it exists
#expect(e.hasNamedPolicy(ptype: "p", params: ["alice", "data1", "read"]))

// Remove it
let removed = try await e.removeNamedPolicy(ptype: "p", params: ["alice", "data1", "read"])
#expect(removed == true)

// Verify it's gone
#expect(!e.hasNamedPolicy(ptype: "p", params: ["alice", "data1", "read"]))
}
}

@Test("async addNamedPolicies and removeNamedPolicies")
func testAsyncNamedPolicies() async throws {
try await withEnforcer { e in
// Add multiple named policies
let rules = [
["alice", "data1", "read"],
["bob", "data2", "write"]
]
let added = try await e.addNamedPolicies(ptype: "p", paramss: rules)
#expect(added == true)

// Verify they exist
#expect(e.hasNamedPolicy(ptype: "p", params: ["alice", "data1", "read"]))
#expect(e.hasNamedPolicy(ptype: "p", params: ["bob", "data2", "write"]))

// Remove them
let removed = try await e.removeNamedPolicies(ptype: "p", paramss: rules)
#expect(removed == true)

// Verify they're gone
#expect(!e.hasNamedPolicy(ptype: "p", params: ["alice", "data1", "read"]))
#expect(!e.hasNamedPolicy(ptype: "p", params: ["bob", "data2", "write"]))
}
}

@Test("async removeFilteredNamedPolicy")
func testAsyncRemoveFilteredNamedPolicy() async throws {
try await withEnforcer { e in
// Add multiple policies
_ = try await e.addNamedPolicy(ptype: "p", params: ["alice", "data1", "read"])
_ = try await e.addNamedPolicy(ptype: "p", params: ["alice", "data2", "write"])
_ = try await e.addNamedPolicy(ptype: "p", params: ["bob", "data1", "read"])

// Remove filtered - all alice policies
let removed = try await e.removeFilteredNamedPolicy(
ptype: "p",
fieldIndex: 0,
fieldValues: ["alice"]
)
#expect(removed == true)

// Verify only bob's policy remains
#expect(!e.hasNamedPolicy(ptype: "p", params: ["alice", "data1", "read"]))
#expect(!e.hasNamedPolicy(ptype: "p", params: ["alice", "data2", "write"]))
#expect(e.hasNamedPolicy(ptype: "p", params: ["bob", "data1", "read"]))
}
}

@Test("async addNamedGroupingPolicy and removeNamedGroupingPolicy")
func testAsyncNamedGroupingPolicy() async throws {
try await withEnforcer { e in
// Add a grouping policy
let added = try await e.addNamedGroupingPolicy(ptype: "g", params: ["alice", "admin"])
#expect(added == true)

// Verify it exists
#expect(e.hasGroupingNamedPolicy(ptype: "g", params: ["alice", "admin"]))

// Remove it
let removed = try await e.removeNamedGroupingPolicy(ptype: "g", params: ["alice", "admin"])
#expect(removed == true)

// Verify it's gone
#expect(!e.hasGroupingNamedPolicy(ptype: "g", params: ["alice", "admin"]))
}
}

@Test("async addNamedGroupingPolicies and removeNamedGroupingPolicies")
func testAsyncNamedGroupingPolicies() async throws {
try await withEnforcer { e in
// Add multiple grouping policies
let rules = [
["alice", "admin"],
["bob", "user"]
]
let added = try await e.addNamedGroupingPolicies(ptype: "g", paramss: rules)
#expect(added == true)

// Verify they exist
#expect(e.hasGroupingNamedPolicy(ptype: "g", params: ["alice", "admin"]))
#expect(e.hasGroupingNamedPolicy(ptype: "g", params: ["bob", "user"]))

// Remove them
let removed = try await e.removeNamedGroupingPolicies(ptype: "g", paramss: rules)
#expect(removed == true)

// Verify they're gone
#expect(!e.hasGroupingNamedPolicy(ptype: "g", params: ["alice", "admin"]))
#expect(!e.hasGroupingNamedPolicy(ptype: "g", params: ["bob", "user"]))
}
}

@Test("async removeFilteredNamedGroupingPolicy")
func testAsyncRemoveFilteredNamedGroupingPolicy() async throws {
try await withEnforcer { e in
// Add multiple grouping policies
_ = try await e.addNamedGroupingPolicy(ptype: "g", params: ["alice", "admin"])
_ = try await e.addNamedGroupingPolicy(ptype: "g", params: ["alice", "user"])
_ = try await e.addNamedGroupingPolicy(ptype: "g", params: ["bob", "user"])

// Remove filtered - all alice grouping policies
let removed = try await e.removeFilteredNamedGroupingPolicy(
ptype: "g",
fieldIndex: 0,
fieldValues: ["alice"]
)
#expect(removed == true)

// Verify only bob's grouping policy remains
#expect(!e.hasGroupingNamedPolicy(ptype: "g", params: ["alice", "admin"]))
#expect(!e.hasGroupingNamedPolicy(ptype: "g", params: ["alice", "user"]))
#expect(e.hasGroupingNamedPolicy(ptype: "g", params: ["bob", "user"]))
}
}
}
Loading