PageSourceSearch

https://www.openpolicyagent.org/assets/js/61c3029d.2cc792ce.js

js openpolicyagent.org collected 2026-09-24 08:29:07 UTC 8,970 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkopa_website=self.webpackChunkopa_website||[]).push([[14135],{28453:(e,n,r)=>{r.d(n,{R:()=>i,x:()=>a});var o=r(96540);const t={},s=o.createContext(t);function i(e){const n=o.useContext(s);return o.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:i(e.components),o.createElement(s.Provider,{value:n},e.children)}},77013:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>l,contentTitle:()=>a,default:()=>u,frontMatter:()=>i,metadata:()=>o,toc:()=>c});const o=JSON.parse('{"id":"rules/idiomatic/prefer-set-or-object-rule","title":"prefer-set-or-object-rule","description":"Summary: Prefer set or object rule over comprehension","source":"@site/projects/regal/rules/idiomatic/prefer-set-or-object-rule.md","sourceDirName":"rules/idiomatic","slug":"/rules/idiomatic/prefer-set-or-object-rule","permalink":"/projects/regal/rules/idiomatic/prefer-set-or-object-rule","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{},"sidebar":"autoSidebar","previous":{"title":"prefer-equals-comparison","permalink":"/projects/regal/rules/idiomatic/prefer-equals-comparison"},"next":{"title":"single-item-in","permalink":"/projects/regal/rules/idiomatic/single-item-in"}}');var t=r(74848),s=r(28453);const i={},a="prefer-set-or-object-rule",l={},c=[{value:"Rationale",id:"rationale",level:2},{value:"Readability",id:"readability",level:3},{value:"Extensibility",id:"extensibility",level:3},{value:"Exceptions",id:"exceptions",level:2},{value:"Configuration Options",id:"configuration-options",level:2},{value:"Related Resources",id:"related-resources",level:2}];function d(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"prefer-set-or-object-rule",children:"prefer-set-or-object-rule"})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Summary"}),": Prefer set or object rule over comprehension"]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Category"}),": Idiomatic"]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"Avoid"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:'package policy\n\n# top level set comprehension\ndevelopers := {developer |\n    some user in input.users\n    "developer" in user.roles\n    developer := user.name\n}\n\n# top level object comprehension\nuser_roles_mapping := {user: roles |\n    some user in input.users\n    roles := user.roles\n}\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.strong,{children:"Prefer"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:'package policy\n\n# set generating rule\ndevelopers contains developer if {\n    some user in input.users\n    "developer" in user.roles\n    developer := user.name\n}\n\n# object generating rule\nuser_roles_mapping[user] := roles if {\n    some user in input.users\n    roles := user.roles\n}\n'})}),"\n",(0,t.jsx)(n.h2,{id:"rationale",children:"Rationale"}),"\n",(0,t.jsxs)(n.p,{children:["Comprehensions are ",(0,t.jsx)(n.a,{href:"https://web.archive.org/web/https://www.styra.com/blog/five-things-you-didnt-know-about-opa/",children:"awesome"}),",\nand should be part of\nany policy author's toolbox. Using comprehensions inside of rule bodies allow for a wide variety of elegant solutions to\notherwise hard problems. However, when used as the value directly (and unconditionally) assigned to a rule, it is almost\nalways better to use a rule that generates a set or object in the rule body rather than having a comprehension do so in\nthe rule head. Why is that?"]}),"\n",(0,t.jsx)(n.h3,{id:"readability",children:"Readability"}),"\n",(0,t.jsx)(n.p,{children:"Rules that generate objects, and sets even more so, read more natural than comprehensions, and are generally more\ndescriptive. While both constructs are easy to spot for a seasoned Rego author, anything that helps improve readability\nis a win."}),"\n",(0,t.jsx)(n.h3,{id:"extensibility",children:"Extensibility"}),"\n",(0,t.jsxs)(n.p,{children:["While readability is important, the real benefit of using a rule to generate a set or object is that it allows the rule\nto be ",(0,t.jsx)(n.em,{children:"extended"}),". This is particularly true for set generating rules, and it's not by accident they often are referred\nto as ",(0,t.jsx)(n.strong,{children:"multi-value rules"}),". A rule assigned the value of a set comprehension can't have its value changed later, or\nmore items added to the set. A set generating rule however, can easily be extended to contain more items, conditionally\nor unconditionally."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:'package policy\n\n# Getting developers from input\ndevelopers contains developer if {\n    some user in input.users\n    "developer" in user.roles\n    developer := user.name\n}\n\n# *Also* getting developers from data\ndevelopers contains developer if {\n    some user in data.users\n    "developer" in user.roles\n    developer := user.name\n}\n\n# Unconditionally adding a developer to the set\ndevelopers contains "Hackerman"\n'})}),"\n",(0,t.jsxs)(n.p,{children:["In the example above, all three rules contribute to the ",(0,t.jsx)(n.code,{children:"developers"})," set. If we wanted to, we could even create another\npolicy file using the same package, and have more rules added there that would contribute to the set. This creates s
1ome\ngreat opportunities for extensibility, and collaboration across developers and teams working on policy together."]}),"\n",(0,t.jsx)(n.p,{children:"Objects differ somewhat from sets in that while several rules can be used to generate an object, there cannot be more\nthan one rule contributing to a single key-value pair."}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:'package policy\n\nnovels[title] := content if {\n    some document in input.documents\n    document.type == "novel"\n    title := document.title\n    content := document.content\n}\n\n# This works as long as "The Hobbit" is not already in the novels object\nnovels["The Hobbit"] := "In a hole in the ground there lived a hobbit."\n\n# Map and set generating objects can also be combined, in which case the\n# value is extensible even for the same key! In the example above, more\n# rules could help contribute titles to an author, perhaps using different\n# data sources.\ntitles_by_author[document.author] contains document.title if {\n    some document in input.documents\n}\n'})}),"\n",(0,t.jsx)(n.h2,{id:"exceptions",children:"Exceptions"}),"\n",(0,t.jsxs)(n.p,{children:["Note that this rule does ",(0,t.jsx)(n.strong,{children:"not"})," apply to array comprehensions, as there is no equivalent tp use a rule to generate an\narray."]}),"\n",(0,t.jsx)(n.p,{children:"This rule will also ignore simple comprehensions used solely for the purpose of converting an array to a set, i.e:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rego",children:"package policy\n\n# Convert set to array. This is fine.\nmy_set := {item | some item in arr}\n"})}),"\n",(0,t.jsx)(n.h2,{id:"configuration-options",children:"Configuration Options"}),"\n",(0,t.jsx)(n.p,{children:"This linter rule provides the following configuration options:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-yaml",children:'rules:\n  idiomatic:\n    prefer-set-or-object-rule:\n      # one of "error", "warning", "ignore"\n      level: error\n'})}),"\n",(0,t.jsx)(n.h2,{id:"related-resources",children:"Related Resources"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["OPA Docs: ",(0,t.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/policy-language/#generating-sets",children:"Generating Sets"})]}),"\n",(0,t.jsxs)(n.li,{children:["OPA Docs: ",(0,t.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/policy-language/#generating-objects",children:"Generating Objects"})]}),"\n",(0,t.jsxs)(n.li,{children:["OPA Docs: ",(0,t.jsx)(n.a,{href:"https://www.openpolicyagent.org/docs/policy-language/#comprehensions",children:"Comprehensions"})]}),"\n",(0,t.jsxs)(n.li,{children:["Styra Blog: ",(0,t.jsx)(n.a,{href:"https://web.archive.org/web/https://www.styra.com/blog/five-things-you-didnt-know-about-opa/",children:"Five Things You Didn't Know About OPA"})]}),"\n",(0,t.jsxs)(n.li,{children:["GitHub: ",(0,t.jsx)(n.a,{href:"https://github.com/open-policy-agent/regal/blob/main/bundle/regal/rules/idiomatic/prefer-set-or-object-rule/prefer_set_or_object_rule.rego",children:"Source Code"})]}),"\n"]})]})}function u(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.