Module: LcpRuby::Tasks::PermissionResolveFormatter
- Defined in:
- lib/lcp_ruby/tasks/permission_resolve_formatter.rb
Overview
Renders a merged ‘Metadata::PermissionDefinition` for the `lcp_ruby:permissions:resolve` rake task. Three formats:
text — human-readable, with `(from <file>)` annotations sourced
from `merged.source_map`. The default.
yaml — round-trippable YAML representation of the definition.
json — same content as YAML but JSON-encoded for tooling.
Pure module — no I/O, no Rails. Tests live in spec/lib/lcp_ruby/tasks/permission_resolve_formatter_spec.rb.
Class Method Summary collapse
-
.annotate(perm, key) ⇒ Object
───────────────────────────────────────────────────────────────────── helpers ─────────────────────────────────────────────────────────────────────.
- .annotate_block(perm, block, name) ⇒ Object
-
.as_hash(perm, role_name) ⇒ Object
───────────────────────────────────────────────────────────────────── serialization ─────────────────────────────────────────────────────────────────────.
- .format_field_list(list) ⇒ Object
-
.format_inherits(inherits) ⇒ Object
Human-readable flat string for the ‘text` format — not parseable back into a PermissionDefinition, but easy to scan in CLI output.
- .format_scope(scope) ⇒ Object
- .json(perm, role_name = nil) ⇒ Object
-
.render_full(perm) ⇒ Object
───────────────────────────────────────────────────────────────────── text: full ─────────────────────────────────────────────────────────────────────.
-
.render_role(perm, role_name) ⇒ Object
───────────────────────────────────────────────────────────────────── text: single role ─────────────────────────────────────────────────────────────────────.
-
.serialize_inherits(inherits) ⇒ Object
Round-trippable structural form for the ‘yaml` / `json` formats.
-
.stringify_keys(map) ⇒ Object
source_map shape: { roles: { name => :sym }, field_overrides: { field => :sym }, record_rules: { name => :sym }, default_role: :sym, inherits_from: :sym|nil } JSON/YAML want strings, but nil values must round-trip as nil — not “”.
- .text(perm, role_name = nil) ⇒ Object
- .yaml(perm, role_name = nil) ⇒ Object
Class Method Details
.annotate(perm, key) ⇒ Object
─────────────────────────────────────────────────────────────────────helpers ─────────────────────────────────────────────────────────────────────
171 172 173 174 175 176 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 171 def annotate(perm, key) return "" unless perm.source_map source = perm.source_map[key] return "" unless source " (#{source})" end |
.annotate_block(perm, block, name) ⇒ Object
178 179 180 181 182 183 184 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 178 def annotate_block(perm, block, name) return "" unless perm.source_map return "" if name.nil? source = perm.source_map.dig(block, name.to_s) return "" unless source source == :default ? " (inherited from _default)" : " (per-model)" end |
.as_hash(perm, role_name) ⇒ Object
─────────────────────────────────────────────────────────────────────serialization ─────────────────────────────────────────────────────────────────────
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 149 def as_hash(perm, role_name) h = { "model" => perm.model, "default_role" => perm.default_role, "roles" => perm.roles, "field_overrides" => perm.field_overrides, "record_rules" => perm.record_rules } h["inherits_from"] = serialize_inherits(perm.inherits_from) if perm.inherits_from h["source_path"] = perm.source_path if perm.source_path h["source_type"] = perm.source_type if perm.source_type h["source_map"] = stringify_keys(perm.source_map) if perm.source_map if role_name && perm.roles.key?(role_name) h["roles"] = { role_name => perm.roles[role_name] } end h end |
.format_field_list(list) ⇒ Object
186 187 188 189 190 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 186 def format_field_list(list) return "all" if list == "all" return "[]" if list.nil? || (list.respond_to?(:empty?) && list.empty?) Array(list).inspect end |
.format_inherits(inherits) ⇒ Object
Human-readable flat string for the ‘text` format — not parseable back into a PermissionDefinition, but easy to scan in CLI output.
198 199 200 201 202 203 204 205 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 198 def format_inherits(inherits) return nil unless inherits parts = [] parts << "parents=#{inherits[:parents].join(',')}" parts << "via=#{inherits[:via]}" if inherits[:via] parts << "mode=#{inherits[:mode]}" parts.join(" ") end |
.format_scope(scope) ⇒ Object
192 193 194 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 192 def format_scope(scope) scope == "all" ? "all" : scope.inspect end |
.json(perm, role_name = nil) ⇒ Object
31 32 33 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 31 def json(perm, role_name = nil) JSON.pretty_generate(as_hash(perm, role_name)) end |
.render_full(perm) ⇒ Object
─────────────────────────────────────────────────────────────────────text: full ─────────────────────────────────────────────────────────────────────
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 39 def render_full(perm) lines = [] lines << "Permission for model '#{perm.model}' (after coverage merge)" lines << "" lines << " source_path: #{perm.source_path || '(unknown)'}" lines << " source_type: #{perm.source_type || '(unknown)'}" lines << " default_role: #{perm.default_role}#{annotate(perm, :default_role)}" lines << " inherits_from: #{format_inherits(perm.inherits_from) || '—'}" lines << "" lines << " roles:" if perm.roles.empty? lines << " (none)" else perm.roles.each do |name, config| lines << " #{name}:#{annotate_block(perm, :roles, name)}" lines << " crud: #{Array(config['crud']).join(', ')}" readable = config.dig("fields", "readable") writable = config.dig("fields", "writable") if readable || writable lines << " fields: readable=#{format_field_list(readable)} " \ "writable=#{format_field_list(writable)}" end scope = config["scope"] lines << " scope: #{format_scope(scope)}" if scope end end lines << "" lines << " field_overrides:" if perm.field_overrides.empty? lines << " (none)" else perm.field_overrides.each do |field, override| lines << " #{field}:#{annotate_block(perm, :field_overrides, field)} #{override.inspect}" end end lines << "" lines << " record_rules (evaluation order):" if perm.record_rules.empty? lines << " (none)" else perm.record_rules.each_with_index do |rule, i| name = LcpRuby::Metadata::PermissionDefinition.rule_name(rule) lines << " #{i + 1}. #{name || '(anonymous)'}#{annotate_block(perm, :record_rules, name)}" end end lines.join("\n") end |
.render_role(perm, role_name) ⇒ Object
─────────────────────────────────────────────────────────────────────text: single role ─────────────────────────────────────────────────────────────────────
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 95 def render_role(perm, role_name) unless perm.roles.key?(role_name) available = perm.roles.keys.sort return [ "Role '#{role_name}' not defined in the merged permission for '#{perm.model}'.", "Available roles: #{available.empty? ? '(none)' : available.join(', ')}" ].join("\n") end config = perm.roles[role_name] crud = Array(config["crud"]).map(&:to_s) lines = [] lines << "Effective permissions for role '#{role_name}' on model '#{perm.model}'" lines << "" LcpRuby::Metadata::ConfigurationValidator::VALID_CRUD_ACTIONS.each do |action| mark = crud.include?(action) ? "✓ allowed" : "✗ denied" lines << " #{action.ljust(20)} #{mark}" end lines << "" readable = config.dig("fields", "readable") writable = config.dig("fields", "writable") lines << " readable fields: #{format_field_list(readable)}" lines << " writable fields: #{format_field_list(writable)}" lines << "" scope = config["scope"] lines << " scope: #{scope ? format_scope(scope) : '(none — all visible records)'}" lines << "" applicable = perm.record_rules.select do |rule| next false unless rule.is_a?(Hash) except = Array(rule.dig("effect", "except_roles")).map(&:to_s) !except.include?(role_name) end if applicable.any? lines << " record_rules touching this role:" applicable.each do |rule| name = rule["name"] || "(anonymous)" denied = Array(rule.dig("effect", "deny_crud")).map(&:to_s) cond = rule["condition"] cond_summary = cond.is_a?(Hash) ? "field=#{cond['field']} #{cond['operator']} #{cond['value'].inspect}" : "(complex condition)" lines << " #{name}#{annotate_block(perm, :record_rules, name)}: denies #{denied.join(', ')} when #{cond_summary}" end end lines.join("\n") end |
.serialize_inherits(inherits) ⇒ Object
Round-trippable structural form for the ‘yaml` / `json` formats. Mirrors the shapes that `PermissionDefinition#normalize_inherits` accepts: a bare String for the simple single-parent intersection case, an Array of strings for multi-parent intersection, or a Hash with `parent` / `via` / `mode` for everything else.
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 212 def serialize_inherits(inherits) return nil unless inherits parents = Array(inherits[:parents]) via = inherits[:via] mode = inherits[:mode] || "intersection" if via.nil? && mode == "intersection" parents.size == 1 ? parents.first : parents else h = { "parent" => parents.size == 1 ? parents.first : parents } h["via"] = via if via h["mode"] = mode h end end |
.stringify_keys(map) ⇒ Object
source_map shape: { roles: { name => :sym }, field_overrides: { field => :sym },
record_rules: { name => :sym }, default_role: :sym, inherits_from: :sym|nil }
JSON/YAML want strings, but nil values must round-trip as nil — not “”.
232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 232 def stringify_keys(map) return nil if map.nil? map.each_with_object({}) do |(key, value), out| out[key.to_s] = case value when Hash then value.transform_values { |v| v&.to_s } when nil then nil else value.to_s end end end |
.text(perm, role_name = nil) ⇒ Object
19 20 21 22 23 24 25 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 19 def text(perm, role_name = nil) if role_name render_role(perm, role_name) else render_full(perm) end end |
.yaml(perm, role_name = nil) ⇒ Object
27 28 29 |
# File 'lib/lcp_ruby/tasks/permission_resolve_formatter.rb', line 27 def yaml(perm, role_name = nil) as_hash(perm, role_name).to_yaml end |