Permissions
A module can declare a permissions: tree — a nested set of access control
permissions the module contributes. Each permission has a key (its slug in the
generated identifier) and a name (a programmatic identifier the compiler uses to
name the generated Go var for it, e.g. name: managePosts generates var ManagePostsPermission). The identifier itself is either set explicitly via
fullKey: or computed by the compiler as {parent.fullKey}.{key} while the module
is preprocessed. If key is left empty too, it’s normalized from name (e.g.
name: managePosts with no key becomes key: managePosts).
name: blogpermissions: - key: post name: managePosts title: en: Manage posts description: en: Permissions related to blog posts children: - key: create name: createPost title: en: Create post - key: publish name: publishPost title: en: Publish postpost gets no explicit fullKey, so the compiler derives one: post. Its children
inherit that as their prefix, becoming post.create and post.publish. Because
post groups children and left its key for the compiler to derive, its generated
key gets a trailing .* — post.* — signaling it covers itself and everything below
it. A fullKey set explicitly is always used as-is, even on a node with children,
since the author chose that exact string on purpose.
Compiling this produces Permissions.go:
- one exported var per root permission — not a single var wrapping the whole
forest, and no separate flat constant per node either (unlike the JS/Kotlin/Swift
compilers) since one would just be the same string under a second, disconnected
name to keep in sync. Each var is a fully typed, nested struct: every node embeds
emigo.Permission(so its ownKeyis promoted) and has one field per child, so your editor can autocomplete straight down it with no map lookup and no risk of a typo’d key only failing at runtime, e.g.ManagePostsPermission.CreatePost.Key. A root with no children collapses to a plainemigo.Permissionvalue. A root var’s own name is always PascalCase with a trailingPermission(e.g.managePosts→ManagePostsPermission) — never the bare name a nested child field gets — so a root reads unambiguously as a permission at a glance and two unrelated single-letter roots can’t collide. - alongside each root var, a
<Name>PermissionListvar: that root permission and every one of its descendants, flattened into its own[]emigo.Permission(e.g.ManagePostsPermissionListnext toManagePostsPermission), built by referencing the root var’s own fields (e.g.ManagePostsPermission.CreatePost) rather than re-declaring separate literals, so it can never drift out of sync with it. One list per root, not a single list combining every root — a module with several unrelated permission groups shouldn’t force every caller to filter one combined list back down to the group it actually cares about.
The Permission type itself is emigo.Permission — it lives once in the shared
emigo runtime instead of being redefined at the top of every generated
Permissions.go.
Each node’s own emigo.Permission{...} value is rendered with Key/Name/Title/
Description each on their own line rather than one long comma-separated line: with
Title/Description often multi-entry maps, one line per node would get
unreadably long fast, and gofmt only aligns a composite literal’s fields onto
separate lines when the source already breaks it that way — it won’t reflow a
long single-line literal into one for you.
package blog
import "github.com/torabian/emi/emigo"
/*** Permission keys generated from the module's permissions tree. */// ManagePostsPermission mirrors the "managePosts" permission node (and everything nested under it),// so it can be navigated directly, e.g. ManagePostsPermission.Key.var ManagePostsPermission = struct { emigo.Permission CreatePost emigo.Permission PublishPost emigo.Permission}{ Permission: emigo.Permission{ Key: "post.*", Name: "managePosts", Title: map[string]string{"en": "Manage posts"}, Description: map[string]string{"en": "Permissions related to blog posts"}, }, CreatePost: emigo.Permission{ Key: "post.create", Name: "createPost", Title: map[string]string{"en": "Create post"}, Description: nil, }, PublishPost: emigo.Permission{ Key: "post.publish", Name: "publishPost", Title: map[string]string{"en": "Publish post"}, Description: nil, },}
// ManagePostsPermissionList flattens ManagePostsPermission (and everything nested under it) into a single slice,// for anything that wants to walk them all at once (seeding an ACL table,// rendering a permissions picker, ...).var ManagePostsPermissionList = []emigo.Permission{ ManagePostsPermission.Permission, ManagePostsPermission.CreatePost, ManagePostsPermission.PublishPost,}A module with a second, unrelated root permission (e.g. viewReports, with no
children) gets its own separate ViewReportsPermission var and its own separate
ViewReportsPermissionList right alongside it — not folded into
ManagePostsPermissionList.
If a child’s own field name happens to collide with the name a branch node’s self
value would otherwise be embedded under (Permission — e.g. a child literally
named permission), that branch’s self value is instead given the explicit field
name Permission_ so nothing gets silently overwritten, and that root’s List
references it under that same name (e.g. WorkspacesPermission.Permission_ inside
WorkspacesPermissionList).