-
Notifications
You must be signed in to change notification settings - Fork 5k
Introduce a bicep reference builder #51370
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
base: main
Are you sure you want to change the base?
Introduce a bicep reference builder #51370
Conversation
API Change CheckAPIView identified API level changes in this PR and created the following API reviews |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a bicep reference builder that enables generating proper Bicep expressions from BicepValue objects, addressing issue #51371. The implementation provides a way to convert BicepValue instances to their corresponding Bicep reference expressions rather than their literal values.
Key changes include:
- Added
ToBicepExpression
extension methods for converting BicepValue objects to Bicep reference expressions - Implemented specialized indexer classes for list and dictionary access patterns
- Enhanced list and dictionary indexers to handle out-of-bounds and missing key scenarios properly
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
File | Description |
---|---|
BicepValueReferenceExtensions.cs | Core extension methods for converting BicepValue to BicepExpression references |
BicepListIndexer.cs | Internal class handling list indexer references with proper error handling |
BicepDictionaryIndexer.cs | Internal class handling dictionary indexer references with proper error handling |
BicepListOfT.cs | Enhanced indexer logic to return proper reference types and validate bounds |
BicepDictionaryOfT.cs | Enhanced dictionary indexer to handle missing keys and return reference types |
BicepValue.cs | Made Compile method virtual to allow specialized indexer behavior |
BicepValueReferenceExtensionsTests.cs | Comprehensive test suite covering all reference generation scenarios |
sdk/provisioning/Azure.Provisioning/src/BicepDictionaryIndexer.cs
Outdated
Show resolved
Hide resolved
sdk/provisioning/Azure.Provisioning/tests/Expressions/BicepValueReferenceExtensionsTests.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ueReferenceExtensionsTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
public static Azure.Provisioning.Expressions.BicepExpression ToBicepExpression(this Azure.Provisioning.IBicepValue value) { throw null; } | ||
public static Azure.Provisioning.Expressions.BicepExpression ToBicepExpression<T>(this Azure.Provisioning.BicepValue<T> value) { throw null; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all the properties with "primitive" types would be of a type of BicepValue<T>
.
The IBicepValue
overload is here to take up the cases for BicepList<T>
, BicepDictionary<T>
, ProvisionableConstruct
and other stuffs that I did not notice.
Should we change this to a set of more explicit overloads that explicitly takes the above variants?
@@ -75,7 +75,7 @@ internal BicepValue() { } | |||
public virtual bool IsEmpty { get { throw null; } } | |||
void Azure.Provisioning.IBicepValue.Assign(Azure.Provisioning.IBicepValue source) { } | |||
void Azure.Provisioning.IBicepValue.SetReadOnly() { } | |||
public Azure.Provisioning.Expressions.BicepExpression Compile() { throw null; } | |||
public virtual Azure.Provisioning.Expressions.BicepExpression Compile() { throw null; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the implementation, you could see I add this virtual to make sure we could be possible to throw the exception when the item gets compiled.
We could add a private proected virtual BicepExpression CompileCore()
in order not to change public API.
I notice that this implementation might have a challenge in the case that we use or maybe not - we could say, if you want one of the argument in your interpolated string to be an expression, you need to call the method and use the output expression as the argument. |
} | ||
set | ||
{ | ||
if (_kind == BicepValueKind.Expression || _isOutput) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happened before if someone tried to set such a value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
they will succeed at setting it.
You could refer the implementation of BicepList<T>
- it has this check therefore if they tried to
list[0] = ...
on an output property, it would throw.
// ensure there is a value | ||
if (index >= _values.Count) | ||
{ | ||
return new BicepListIndexer<T>(_self, index); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it safe to assume that if you pass in an index that is too big you meant to get back something that is only present at runtime? I am not sure this assumption is safe here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well this is the part that I once said "the exceptions are deferred".
Here are possible scenarios:
- the list is not output only, and the user used a "valid" index: everything works just fine.
- the list is output, and the user used an index to reference an item:
when the list is output, no one could add items into it, therefore the index must be out of range here - we will always go into this branch. - the list is not output, and the user used a "valid" index: everything works just fine.
- the list is not output, and the user used a "invalid" index: now we do not have a value that we could get from the list. Therefore we go into this branch.
In the 2 cases that we go into this branch, if the user wants to get the value, we throw because there is no such a value. If they want an expression, I think the best we could do is to spit out the expression and let ARM to throw the error, we do not have a way to know if this is valid.
/// <returns></returns> | ||
public static BicepExpression ToBicepExpression<T>(this BicepValue<T> value) | ||
{ | ||
if (value is BicepListIndexer<T> indexer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we use a switch here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will fix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
sdk/provisioning/Azure.Provisioning/src/Primitives/BicepValueReference.cs
Show resolved
Hide resolved
Properties = new TestProperties() | ||
}; | ||
var nestedOutputList = resource.Properties.OutputList; | ||
Assert.AreEqual("[]", nestedOutputList.ToString()); // TODO -- should this throw? or just like right now we return an empty list? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@m-nash what do you think about this?
public void Add(BicepValue<T> item) => _values.Add(item); | ||
public void Insert(int index, BicepValue<T> item) | ||
{ | ||
if (_kind == BicepValueKind.Expression || _isOutput) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let us revert these changes on throwing exceptions
case BicepValueKind.Unset: | ||
return new BicepListIndexer<T>(_self, index); | ||
case BicepValueKind.Expression: | ||
return new BicepListIndexer<T>(_self, index, value.Expression!); | ||
case BicepValueKind.Literal: | ||
return new BicepListIndexer<T>(_self, index, (T)value.LiteralValue!); | ||
default: | ||
throw new InvalidOperationException($"Unknown {value.Kind}!"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change one of these to default
throw new ArgumentOutOfRangeException(nameof(index), "Index must be non-negative."); | ||
} | ||
// ensure there is a value | ||
if (index >= _values.Count) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how would this works on a list of models?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var x = resource.Models[1];
var xName = x.Name;
Fixes #51371
Fixes #48491
This PR introduces two extension methods:
which converts a bicep value instance into an expression.
Also fixes the issue that when we try to use out-of-range index on a list or a non-exist key on a dictionary, the
ArgumentOutOfRangeException
ofKeyNotFoundException
are thrown.Now the exception are deferred to throw until we are trying to access the value in it, but if we convert it to an expression, no exception would be thrown at all.
Contributing to the Azure SDK
Please see our CONTRIBUTING.md if you are not familiar with contributing to this repository or have questions.
For specific information about pull request etiquette and best practices, see this section.