Skip to content
Merged
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
18 changes: 9 additions & 9 deletions docs/superpowers/specs/2026-03-10-caskin-modernization.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ caskin 是一个 Go 的多域 RBAC 权限管理库,基于 casbin 开发。最
### 任务清单

- [x] 升级 Go 到最新版(1.24)
- [ ] 依赖升级(只升无破坏性变更的小版本/patch)
- [ ] 跑通所有现有测试,修复失败的
- [ ] 使用新 Go 语法重构:
- `interface{}` → `any`
- `range` over integers(Go 1.22)
- `slices` / `maps` 标准库替换手写循环
- `min()` / `max()` 内置函数
- 适当引入泛型简化重复代码
- [ ] 完善 README:补充真实可运行的 Quick Start 示例
- [x] 依赖升级(只升无破坏性变更的小版本/patch)
- [x] 完善 README:补充真实可运行的 Quick Start 示例(PR #26,已合并)
- [x] 使用新 Go 语法重构(PR #27)
- 完全移除 `go-linq` 和 `golang.org/x/exp`,替换为 stdlib
- `sort.Slice` → `slices.SortFunc`
- `constraints.Ordered` → `cmp.Ordered`
- BFS 循环加注释增强可读性
- `distinct()` 泛型函数复用
- [ ] 跑通所有现有测试,修复失败的(✅ 当前测试全部通过)
- [ ] 补充 godoc 注释(核心 API)

**卡点:完成后汇报,等用户确认进入 Phase 2**
Expand Down
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ go 1.24

require (
github.com/BurntSushi/toml v1.6.0
github.com/ahmetb/go-linq/v3 v3.2.0
github.com/casbin/casbin/v2 v2.69.0
github.com/casbin/gorm-adapter/v3 v3.17.0
github.com/casbin/redis-watcher/v2 v2.5.0
github.com/redis/go-redis/v9 v9.0.5
github.com/stretchr/testify v1.11.1
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f
gorm.io/driver/mysql v1.5.0
gorm.io/driver/postgres v1.5.0
gorm.io/driver/sqlite v1.5.0
Expand Down
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/ahmetb/go-linq/v3 v3.2.0 h1:BEuMfp+b59io8g5wYzNoFe9pWPalRklhlhbiU3hYZDE=
github.com/ahmetb/go-linq/v3 v3.2.0/go.mod h1:haQ3JfOeWK8HpVxMtHHEMPVgBKiYyQ+f1/kLZh/cj9U=
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
Expand Down Expand Up @@ -112,8 +110,6 @@ golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
Expand Down
10 changes: 5 additions & 5 deletions inheritance.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"cmp"
"encoding/json"
"slices"
"sort"
)

// InheritanceEdge x is node, y is adjacency
Expand All @@ -26,14 +25,14 @@ func (i *InheritanceEdge[T]) Decode(in string) error {
type EdgeSorter[T cmp.Ordered] map[T]int

func (e EdgeSorter[T]) RootFirstSort(edges []*InheritanceEdge[T]) {
sort.Slice(edges, func(i, j int) bool {
return e[edges[i].U] < e[edges[j].U]
slices.SortFunc(edges, func(a, b *InheritanceEdge[T]) int {
return cmp.Compare(e[a.U], e[b.U])
})
}

func (e EdgeSorter[T]) LeafFirstSort(edges []*InheritanceEdge[T]) {
sort.Slice(edges, func(i, j int) bool {
return e[edges[i].V] > e[edges[j].V]
slices.SortFunc(edges, func(a, b *InheritanceEdge[T]) int {
return cmp.Compare(e[b.V], e[a.V])
})
}

Expand Down Expand Up @@ -79,6 +78,7 @@ func (g InheritanceGraph[T]) TopSort() []T {
queue = append(queue, k)
}
}
// BFS topological sort: queue grows as in-degrees reach zero
for i := 0; i < len(queue); i++ {
node := queue[i]
for _, v := range g[node] {
Expand Down
6 changes: 2 additions & 4 deletions object_directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@ func (o *objectDirectory) Search(prefix uint64, searchType DirectorySearchType)

switch searchType {
case DirectorySearchAll:
// BFS: process queue while it grows with children
for i := 0; i < len(out); i++ {
node := out[i]
for _, v := range o.Tree[node.GetID()] {
out = append(out, v)
}
out = append(out, o.Tree[out[i].GetID()]...)
}
default:
}
Expand Down
32 changes: 24 additions & 8 deletions server.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package caskin

import (
"github.com/ahmetb/go-linq/v3"
"cmp"

"github.com/casbin/casbin/v2"
"github.com/casbin/gorm-adapter/v3"
"golang.org/x/exp/constraints"
)

type server struct {
Expand Down Expand Up @@ -83,17 +83,33 @@ func Check[T any](e IEnforcer, u User, d Domain, one T, action Action) bool {
return false
}

// Diff do diff source, target list to get add, remove list
func Diff[T constraints.Ordered](source, target []T) (add, remove []T) {
linq.From(source).Except(linq.From(target)).ToSlice(&remove)
linq.From(target).Except(linq.From(source)).ToSlice(&add)
// Diff returns the elements to add and remove to transform source into target.
func Diff[T cmp.Ordered](source, target []T) (add, remove []T) {
sourceSet := make(map[T]struct{}, len(source))
for _, v := range source {
sourceSet[v] = struct{}{}
}
targetSet := make(map[T]struct{}, len(target))
for _, v := range target {
targetSet[v] = struct{}{}
}
for _, v := range source {
if _, ok := targetSet[v]; !ok {
remove = append(remove, v)
}
}
for _, v := range target {
if _, ok := sourceSet[v]; !ok {
add = append(add, v)
}
}
return
}

// DiffPolicy diff policy source, target list to get add, remove list
func DiffPolicy(source, target []*Policy) (add, remove []*Policy) {
sourceMap := make(map[any]*Policy)
targetMap := make(map[any]*Policy)
sourceMap := make(map[string]*Policy, len(source))
targetMap := make(map[string]*Policy, len(target))
for _, v := range source {
sourceMap[v.Key()] = v
}
Expand Down
1 change: 1 addition & 0 deletions server_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func objectHierarchyBFS(domain Domain, object Object, fn func(Object, Domain) []
hierarchyLevel := 0
m := map[uint64]int{object.GetID(): 1}
queue := []uint64{object.GetID()}
// BFS: queue grows as children are discovered
for i := 0; i < len(queue); i++ {
node := newByE(object)
node.SetID(queue[i])
Expand Down
13 changes: 6 additions & 7 deletions server_feature_reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,21 @@ func (s *server) ResetFeature(domain Domain) error {
}

func getSourceFeatureG2(e IEnforcer, domain Domain) map[string][]string {
var queue []string
queue := []string{DefaultFeatureRootName}
inQueue := map[string]bool{DefaultFeatureRootName: true}
for _, v := range queue {
inQueue[v] = true
}

m := map[string][]string{}
// BFS: queue grows as children are discovered
for i := 0; i < len(queue); i++ {
m[queue[i]] = []string{}
ll := e.GetChildrenForObjectInDomain(&NamedObject{Name: queue[i]}, domain)
current := queue[i]
m[current] = []string{}
ll := e.GetChildrenForObjectInDomain(&NamedObject{Name: current}, domain)
for _, v := range ll {
if _, ok := inQueue[v.Encode()]; !ok {
queue = append(queue, v.Encode())
inQueue[v.Encode()] = true
}
m[queue[i]] = append(m[queue[i]], v.Encode())
m[current] = append(m[current], v.Encode())
}
}

Expand Down
5 changes: 1 addition & 4 deletions server_policy.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package caskin

import (
"github.com/ahmetb/go-linq/v3"
)

// GetPolicy
// get all policies
Expand Down Expand Up @@ -84,7 +81,7 @@ func (s *server) ModifyPolicyPerRole(user User, domain Domain, perRole Role, inp
}
oid = append(oid, oid1...)
oid = append(oid, oid2...)
linq.From(oid).Distinct().ToSlice(&oid)
oid = distinct(oid)
objects, err := s.DB.GetObjectByID(oid)
if err != nil {
return err
Expand Down
6 changes: 2 additions & 4 deletions server_user_domain.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package caskin

import "github.com/ahmetb/go-linq/v3"

// GetUserByDomain
// get all user in domain
// 1. no permission checking
func (s *server) GetUserByDomain(domain Domain) ([]User, error) {
us := s.Enforcer.GetUsersInDomain(domain)
uid := ID(us)
linq.From(uid).Distinct().ToSlice(&uid)
uid = distinct(uid)
return s.DB.GetUserByID(uid)
}

Expand All @@ -21,7 +19,7 @@ func (s *server) GetDomainByUser(user User) ([]Domain, error) {
}
ds := s.Enforcer.GetDomainsIncludeUser(user)
did := ID(ds)
linq.From(did).Distinct().ToSlice(&did)
did = distinct(did)
return s.DB.GetDomainByID(did)
}

Expand Down
8 changes: 2 additions & 6 deletions server_user_role.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package caskin

import (
"github.com/ahmetb/go-linq/v3"
)

// GetUserRole
// 1. get all user
// 2. get all role which current user has read permission in current domain
Expand Down Expand Up @@ -96,7 +92,7 @@ func (s *server) ModifyUserRolePerUser(user User, domain Domain, perUser User, i
var rid []uint64
rid = append(rid, rid1...)
rid = append(rid, rid2...)
linq.From(rid).Distinct().ToSlice(&rid)
rid = distinct(rid)
roles, err := s.DB.GetRoleByID(rid)
if err != nil {
return err
Expand Down Expand Up @@ -152,7 +148,7 @@ func (s *server) ModifyUserRolePerRole(user User, domain Domain, perRole Role, i
var uid []uint64
uid = append(uid, uid1...)
uid = append(uid, uid2...)
linq.From(uid).Distinct().ToSlice(&uid)
uid = distinct(uid)
users, err := s.DB.GetUserByID(uid)
if err != nil {
return err
Expand Down
Loading