docs: add usage examples (multi-domain, role inheritance, permission checks, frontend/backend)#31
docs: add usage examples (multi-domain, role inheritance, permission checks, frontend/backend)#31AWaterColorPen wants to merge 5 commits intomainfrom
Conversation
…on checks, and frontend/backend separation
AWaterColorPen
left a comment
There was a problem hiding this comment.
The structure and scenario coverage are excellent. Four compilation-blocking issues need fixing before merge.
|
|
||
| // newService creates an in-memory caskin service for demonstration. | ||
| func newService() (caskin.IService, *gorm.DB) { | ||
| dir, _ := os.MkdirTemp("", "caskin-example-*") |
There was a problem hiding this comment.
Missing caskin.Register[...]() call
All real usages of caskin (playground, tests) call caskin.Register[*example.User, *example.Role, *example.Object, *example.Domain]() before constructing the service. Without it the factory does not know which concrete types to instantiate and panics at runtime. Please add this call to newService() — see the corrected snippet in the comment on caskin.New below.
There was a problem hiding this comment.
Fixed. Rewrote newService() to use caskin.Options{DB: dbOption, Dictionary: &caskin.DictionaryOption{Dsn: "configs/caskin.toml"}} directly, and added caskin.Register[*example.User, *example.Role, *example.Object, *example.Domain]() before caskin.New. Mirrors playground/playground.go exactly.
| dict, _ := dictOption.Build() | ||
|
|
||
| svc, _ := caskin.New( | ||
| caskin.WithDB(db), |
There was a problem hiding this comment.
Bug: caskin.New does not accept functional options
caskin.WithDB, caskin.WithDictionary, and caskin.DefaultModelText() do not exist in the codebase — this will not compile. caskin.New takes an *Options struct directly, and DictionaryOption only has Dsn/Type fields.
Suggested fix (mirrors playground/playground.go):
func newService() (caskin.IService, *gorm.DB) {
dir, _ := os.MkdirTemp("", "caskin-example-*")
dbOption := &caskin.DBOption{
DSN: dir + "/sqlite",
Type: "sqlite",
}
db, _ := dbOption.NewDB()
_ = db.AutoMigrate(
&example.User{},
&example.Role{},
&example.Object{},
&example.Domain{},
)
caskin.Register[*example.User, *example.Role, *example.Object, *example.Domain]()
svc, _ := caskin.New(&caskin.Options{DB: dbOption})
return svc, db
}There was a problem hiding this comment.
Fixed alongside the Register comment — the whole newService() helper is now rewritten to pass &caskin.Options{DB: dbOption, Dictionary: &caskin.DictionaryOption{Dsn: "configs/caskin.toml"}}. The non-existent WithDB, WithDictionary, and DefaultModelText() helpers are gone.
| // --- Verify that Bob (editor) inherits viewer permissions --- | ||
| // Bob should be able to read (inherited) and write (direct). | ||
| canRead := caskin.Check(svc.GetEnforcer(), bob, domain, resource, caskin.Read) | ||
| canWrite := caskin.Check(svc.GetEnforcer(), bob, domain, resource, caskin.Write) |
There was a problem hiding this comment.
Bug: IService has no GetEnforcer() method
caskin.Check(svc.GetEnforcer(), ...) will not compile — IService does not expose the internal enforcer. This pattern is used in Scenarios 2, 3, and 4.
For a public-API-safe alternative:
// instead of:
canRead := caskin.Check(svc.GetEnforcer(), bob, domain, resource, caskin.Read)
// use:
canRead := svc.CheckObject(bob, domain, resource, caskin.Read) == nilIf showing the low-level bool return is important for the example, the prose should note that caskin.Check requires a concrete *server type rather than IService.
There was a problem hiding this comment.
Fixed. All caskin.Check(svc.GetEnforcer(), ...) calls have been replaced with svc.CheckObject(...) == nil throughout Scenarios 2, 3, and 4 (including the API gateway simulation). The middleware pseudocode now uses svc.CheckObject(...) != nil as the rejection guard. Also updated the intro text and "Choosing the right check" table in Scenario 3 to clarify that caskin.Check requires a concrete enforcer, not IService.
| } | ||
|
|
||
| func filterByType(objs []caskin.Object, ty caskin.ObjectType) []caskin.Object { | ||
| var out []caskin.Object |
There was a problem hiding this comment.
Bug: o.GetType() → o.GetObjectType()
The caskin.Object interface defines GetObjectType() string, not GetType(). The filterByType helper will not compile.
// fix:
func filterByType(objs []caskin.Object, ty caskin.ObjectType) []caskin.Object {
var out []caskin.Object
for _, o := range objs {
if o.GetObjectType() == ty {
out = append(out, o)
}
}
return out
}There was a problem hiding this comment.
Fixed. filterByType now calls o.GetObjectType() as defined by the caskin.Object interface.
- Replace non-existent WithDB/WithDictionary/DefaultModelText helpers
in newService() with direct caskin.Options{DB, Dictionary} struct
- Add required caskin.Register[...U,R,O,D]() call before caskin.New
(mirrors playground/playground.go pattern)
- Replace svc.GetEnforcer() (IService has no such method) with
svc.CheckObject(...) == nil for boolean permission checks
- Fix o.GetType() -> o.GetObjectType() in filterByType helper
- Update Scenario 3 intro and table to reflect corrected API usage
AWaterColorPen
left a comment
There was a problem hiding this comment.
Two more compilation-blocking bugs found after the first round of fixes.
| // --- Verify that Bob (editor) inherits viewer permissions --- | ||
| // Bob should be able to read (inherited) and write (direct). | ||
| // IService exposes CheckObject which returns nil on success. | ||
| canRead := svc.CheckObject(bob, domain, resource, caskin.Read) == nil |
There was a problem hiding this comment.
Bug: IService has no CheckObject method
svc.CheckObject(...) will not compile because CheckObject is defined on the concrete *server type but is not part of the IService interface. svc is typed as caskin.IService, so any call to svc.CheckObject is a compile error.
This affects every CheckObject call in Scenarios 2, 3, and 4 (lines 215, 216, 221, 286, 290, 293, 437, 440, 480).
The public API for permission checks on plain Object items does not appear to be exposed through IService. Recommend verifying against the playground/tests how Object permissions are checked through the service interface, then updating the examples accordingly. One working pattern already shown in the existing codebase is svc.CheckCreateObjectData / svc.CheckModifyObjectData for ObjectData types.
There was a problem hiding this comment.
Fixed in Round 3 (2026-04-24). All svc.CheckObject(...) calls have been replaced with the query-oriented pattern:
objs, _ := svc.GetObject(user, domain, action)
allowed := containsObj(objs, obj.GetID())GetObject returns only the objects the caller is permitted to act on, so checking for presence is semantically equivalent to a direct permission check. A containsObj helper is defined at the bottom of the file and reused throughout Scenarios 2, 3, and 4.
| fmt.Println("eng admin sees marketing roles:", len(rolesSeenByEngAdmin)) // 0 | ||
|
|
||
| // The superadmin can see all domains. | ||
| domains, _ := svc.GetDomain(superadmin) |
There was a problem hiding this comment.
Bug: GetDomain takes no arguments
IService.GetDomain() is declared as GetDomain() ([]Domain, error) — it accepts no parameters. The call svc.GetDomain(superadmin) will not compile.
Fix options:
// Fix A: list all domains (GetDomain returns all, superadmin can see all)
domains, _ := svc.GetDomain()
// Fix B: domains the superadmin is enrolled in
domains, _ := svc.GetDomainByUser(superadmin)There was a problem hiding this comment.
Fixed in Round 3 (2026-04-24). Changed to svc.GetDomain() with no arguments (Fix A). Since GetDomain() returns all domains visible to the caller, and the superadmin can see all domains, this is the correct call for Scenario 1’s listing step.
…eckObject not on IService, GetDomain takes no args)
…ject filter pattern CheckObject exists on *server struct but is NOT exposed in the IService interface. All example code that called svc.CheckObject(...) would fail to compile when svc is typed as IService. Fix approach: switch to caskin's query-oriented pattern \u2014 GetObject(user, domain, action) only returns objects the caller may act on; containsObj() then checks whether the target object is present. This is idiomatic caskin and works through IService. Additional fix: GetDomain() takes no arguments \u2014 removed the spurious 'superadmin' argument in Scenario 1 (interface signature is GetDomain() ([]Domain, error)). Refs PR #31 round-3 review notes.
|
Round 3 fix (2026-04-24): Both compilation-blocking issues from Round 2 have been addressed:
The table in "Choosing the right check" has also been updated to reflect the correct public API surface. Ready for re-review. |
Summary
Phase 2 third task: Usage Examples — practical, self-contained code examples for the four most common caskin scenarios.
Changes
docs/examples.md— covers:caskin.Check(low-level bool),IService.CheckObject(typed error), andICurrentService.Check*WithCurrent(middleware pattern)docs/superpowers/specs/2026-03-10-caskin-modernization.mdto mark this task donePhase 2 progress