Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support @allowed attribute on custom type array values #9395

Open
WhitWaldo opened this issue Dec 29, 2022 · 7 comments
Open

Support @allowed attribute on custom type array values #9395

WhitWaldo opened this issue Dec 29, 2022 · 7 comments
Labels
enhancement New feature or request Needs: Upvote This issue requires more votes to be considered type system

Comments

@WhitWaldo
Copy link

WhitWaldo commented Dec 29, 2022

This is another request related to custom types. I suddenly have a new requirement to spin up a Key Vault instance using Access Policies. I'd like to be able to fully define the desired shape of the per-object policy via custom types, but the lack of support for @allowed stands in the way of that. What I'd like to do is the following:

type accessPolicyPermissions = {
  @allowed([
    'Get'
    'List'
    'Updates'
    'Create'
    'Import'
    'Delete'
    'Recover'
    'Backup'
    'Restore'
    'GetRotationPolicy'
    'SetRotationPolicy'
    'Rotate'
  ])
  keys: string[]
  @allowed([
    'Get'
    'List'
    'Set'
    'Delete'
    'Backup'
    'Recover'
  ])
  secrets: string[]
  @allowed([
    'Get'
    'List'
    'Update'
    'Create'
    'Import'
    'Delete'
    'Recover'
    'Backup'
    'Restore'
    'ManageContacts'
    'ManageIssuers'
    'GetIssuers'
    'ListIssuers'
    'SetIssuers'
    'DeleteIssuers'
  ])
  certificates: string[]
}

type accessPolicy = {
  tenantId: string
  objectId: string
  permissions: accessPolicyPermissions
}

param accessPolicies accessPolicy[] = []

But.. because I cannot today use @allowed, I have to entirely opt out of custom types for this purpose leaving me with either specifying the individual arrays themselves for each permission type or opting out altogether and just doing:

param AccessPolicies array = []

This is far from ideal

Thanks for the consideration!

@WhitWaldo WhitWaldo added the enhancement New feature or request label Dec 29, 2022
@ghost ghost added the Needs: Triage 🔍 label Dec 29, 2022
@jeskew
Copy link
Member

jeskew commented Dec 29, 2022

You can use a union type:

  @allowed([
    'Get'
    'List'
    'Set'
    'Delete'
    'Backup'
    'Recover'
  ])
  secrets: string[]

would become:

  secrets: ('Get' | 'List' | 'Set' | 'Delete' |  'Backup' | 'Recover')[]

@WhitWaldo
Copy link
Author

That did the trick precisely, though my lines are a bit long now. In the name of readability, is there any possibility of having @allowed be made synonymous with a union type, e.g. at build time, simply convert the @allowed value to a union type in the output definition?

type AccessPolicyPermissionsSet = {
  keys: ('Get' | 'List' | 'Update' | 'Create' | 'Import' | 'Delete' | 'Recover' | 'Backup' | 'Restore' | 'GetRotationPolicy' | 'SetRotationPolicy' | 'Rotate')[]
  secrets: ('Get' | 'List' | 'Set' | 'Delete' | 'Recover' | 'Backup' | 'Restore')[]
  certificates: ('Get' | 'List' | 'Update' | 'Create' | 'Import' | 'Delete' | 'Recover' | 'Backup' | 'Restore' | 'ManageContacts' | 'ManageIssuers' | 'GetIssuers' | 'ListIssuers' | 'SetIssuers' | 'DeleteIssuers')[]
}

type AccessPolicy = {
  tenantId: string
  principalId: string
  permissions: AccessPolicyPermissionsSet
}

@jeskew
Copy link
Member

jeskew commented Jan 2, 2023

Generally speaking, Bicep tries to follow a "There should be one-- and preferably only one --obvious way to do it" rule, so alternate syntax for something that can already be expressed is discouraged.

I opened #9441 to allow multiline union types, which may help.

@WhitWaldo
Copy link
Author

WhitWaldo commented Jan 2, 2023

@jeskew I understand the motivation there, but especially as the language evolves, I suspect you'll increasingly find users confused by "approach works in this case, but I have do to something completely different over here" despite precisely the same net effect. If we could go back in time and have either union types or @allowed attributes, I'd have picked only one or the other (though my answer depends on where the custom types validation inquiry goes and the ease of applying sets of validation constraints).

Again, while I'd encourage a "should be only one" approach, please don't make that a hard and fast rule for all syntax as some of these language design ships have already sailed.

@jeskew
Copy link
Member

jeskew commented Apr 18, 2023

Reopening since there was another ask for this and I'd like to keep the discussion consolidated.

@jeskew jeskew reopened this Apr 18, 2023
@jeskew jeskew added enhancement New feature or request story: type system vNext Needs: Upvote This issue requires more votes to be considered and removed enhancement New feature or request Needs: Triage 🔍 labels Apr 18, 2023
@jeskew jeskew added this to Bicep Apr 18, 2023
@github-project-automation github-project-automation bot moved this to Todo in Bicep Apr 18, 2023
@jeskew
Copy link
Member

jeskew commented Apr 18, 2023

If there's strong community support for this ask, I will back down, but I dislike the @allowed decorator and introduced union syntax with the explicit goal of replacing it To preserve backwards compatibility, there are no plans to remove @allowed, but I intentionally did not enable its use on other statements that can now use type declaration syntax (i.e., type and output).

The issues I have with @allowed are:

  1. The Bicep team was encouraged by our language design mentor to only use decorators as a last resort, and there's a lot of prior art from other languages on how to express an enumeration with dedicated syntax. Bicep was already using TypeScript's syntax in hovers, so our users were already familiar with it in a Bicep context.
  2. @allowed has the exact same semantics as ARM's allowedValues constraint, which means different things depending on the primitive type of its target parameter. E.g., @allowed(['fizz', 'buzz', 'pop']) means that a value must be one of 'fizz', 'buzz', or 'pop' when it targets a string param, but it means that a value must contain a subset of {'fizz', 'buzz', 'pop'} (with repeats allowed) when it targets an array.
  3. The secondary (subset) meaning of @allowed can only be used when none of the values are arrays, which means that users would have sometimes needed to shift the decorator from the container to its element type. This hurts composability and could be especially confusing when the container type and its element type are declared separately.

Composability concerns only come into play when type statements are used, which is why I don't think it's too big of problem to leave @allowed in place for param statements.

@WhitWaldo
Copy link
Author

I knew I'd asked this before - I admit, I didn't think to remove the "closed" filter while searching for it earlier. My mistake!

While I think I'd argue that the ship has sailed and that by having the @allowed decorator on primitive types but not custom types will cause greater confusion to developers, I cede your point about the application to arrays.

Might I instead then suggest a rule (don't know if this is a good linting candidate or for some other sort of project analyzer) that when I attempt to specify an @allowed attribute on a custom type, it pops up with a warning in-line (in VS Code, ideally) that indicates that this isn't allowed for custom types and instead links to the union approach? My issue this morning was one of both not recalling this older thread and also not readily seeing an alternative path forward presented to me. If the tooling instead recognized the error of what I was trying to do and properly redirected me towards the "preferred" path, that would be a much better path forward in the longer term, I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Needs: Upvote This issue requires more votes to be considered type system
Projects
Status: Todo
Development

No branches or pull requests

3 participants