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 Swift enum for it, e.g. name: managePosts generates
enum 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.swift:
- one exported
enumnamespace per root permission — not a single enum wrapping the whole forest, and no separate flat constant per node either — so Xcode can autocomplete straight down it, e.g.ManagePostsPermission.CreatePost.key. A node with children is a nested Swiftenumwith no cases (Swift’s idiomatic uninstantiable namespace): its ownkey/name/title/descriptionsit directly on it asstatic lets, alongside one nested enum/let per child (named by the child’s own identifier, PascalCase). A node with no children collapses to a plainlet(or, nested inside an enum,static let) holding aPermissioninstance directly. The self attributes are always lowercase while every child is always PascalCase, so the two can never collide — there’s no case-collision escape hatch to reach for, unlike a language that flattens both into the same casing convention. The root’s own name is always PascalCase with a trailingPermission(e.g.managePosts→ManagePostsPermission), so it reads unambiguously as a permission and can’t collide with an unrelated single-letter root. - alongside each root, a
<Name>PermissionList: [Permission]: that root permission and every one of its descendants, flattened into its own array, by referencing each node’s ownpermissionstatic let 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 struct itself only holds a single node’s own attributes — no
children property — since the tree structure lives in the nested enums above,
not in the data instances; it exists purely as the common type every node’s
permission let and every <Name>PermissionList entry share.
struct Permission: Codable { let key: String let name: String let title: [String: String]? let description: [String: String]?}
// ManagePostsPermission mirrors the "managePosts" permission node (and everything nested under it),// so it can be navigated directly, e.g. ManagePostsPermission.key.enum ManagePostsPermission { static let key: String = "post.*" static let name: String = "managePosts" static let title: [String: String]? = ["en": "Manage posts"] static let description: [String: String]? = ["en": "Permissions related to blog posts"] static let permission: Permission = Permission(key: key, name: name, title: title, description: description)
static let CreatePost: Permission = Permission( key: "post.create", name: "createPost", title: ["en": "Create post"], description: nil )
static let PublishPost: Permission = Permission( key: "post.publish", name: "publishPost", title: ["en": "Publish post"], description: nil )}
// ManagePostsPermissionList flattens ManagePostsPermission (and everything nested under it) into a single array,// for anything that wants to walk them all at once (seeding an ACL table,// rendering a permissions picker, ...).let ManagePostsPermissionList: [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 let ViewReportsPermission: Permission and its own
separate ViewReportsPermissionList right alongside it — not folded into
ManagePostsPermissionList.