Skip to content

Commit

Permalink
Support including imported types in user-defined tagged unions (#13728)
Browse files Browse the repository at this point in the history
Resolves #13661 

User-defined tagged unions currently allow template authors to use type
references as members of the union, but authors will encounter an
unhandled exception if the referent is an imported type. This PR updates
the tagged union member resolution to support imported types,
resource-defined types, and type property accesses.
###### Microsoft Reviewers: [Open in
CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/Azure/bicep/pull/13728)
  • Loading branch information
jeskew authored and StephenWeatherford committed Apr 1, 2024
1 parent 4a30c5b commit e0259b8
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics.CodeAnalysis;
using Bicep.Core.Diagnostics;
using Bicep.Core.UnitTests;
using Bicep.Core.UnitTests.Assertions;
using Bicep.Core.UnitTests.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down Expand Up @@ -643,5 +644,134 @@ public void DiscriminatedObjectUnions_SelfCycle_Inline_Optional()
}
"""));
}

[TestMethod]
public void Issue_13661()
{
var result = CompilationHelper.Compile(
("main.bicep", """
import * as t1 from 'app1.spec.bicep'
import * as t2 from 'app2.spec.bicep'

@discriminator('Name')
type ApiDef = (t1.Api1Def | t2.Api2Def)?

param Name string
param APIs ApiDef[]

output test string = '${Name}-${APIs}'
"""),
("app1.spec.bicep", """
@export()
type Api1Def = {
Name: 'Api1'
Settings: {
CustomSetting1: string
CustomSetting2: string
}
}
"""),
("app2.spec.bicep", """
@export()
type Api2Def = {
Name: 'Api2'
Settings: {
CustomSetting3: string
}
}
"""));

result.Template.Should().NotBeNull();
result.Template.Should().HaveJsonAtPath("definitions.ApiDef.discriminator", """
{
"propertyName": "Name",
"mapping": {
"Api1": {
"$ref": "#/definitions/_1.Api1Def"
},
"Api2": {
"$ref": "#/definitions/_2.Api2Def"
}
}
}
""");
}

[TestMethod]
public void User_defined_discriminated_objects_can_amend_resource_derived_discriminated_unions()
{
var result = CompilationHelper.Compile(
new ServiceBuilder().WithFeatureOverrides(new(TestContext, ResourceDerivedTypesEnabled: true)),
"""
@discriminator('computeType')
type taggedUnion = resource<'Microsoft.MachineLearningServices/workspaces/computes@2020-04-01'>.properties
| { computeType: 'foo', bar: string }
""");

result.Template.Should().NotBeNull();
result.Template.Should().HaveJsonAtPath("definitions.taggedUnion.discriminator", """
{
"propertyName": "computeType",
"mapping": {
"DataFactory": {
"type": "object",
"metadata": {
"__bicep_resource_derived_type!": "Microsoft.MachineLearningServices/workspaces/computes@2020-04-01#properties/properties/discriminator/mapping/DataFactory"
}
},
"Databricks": {
"type": "object",
"metadata": {
"__bicep_resource_derived_type!": "Microsoft.MachineLearningServices/workspaces/computes@2020-04-01#properties/properties/discriminator/mapping/Databricks"
}
},
"VirtualMachine": {
"type": "object",
"metadata": {
"__bicep_resource_derived_type!": "Microsoft.MachineLearningServices/workspaces/computes@2020-04-01#properties/properties/discriminator/mapping/VirtualMachine"
}
},
"AmlCompute": {
"type": "object",
"metadata": {
"__bicep_resource_derived_type!": "Microsoft.MachineLearningServices/workspaces/computes@2020-04-01#properties/properties/discriminator/mapping/AmlCompute"
}
},
"AKS": {
"type": "object",
"metadata": {
"__bicep_resource_derived_type!": "Microsoft.MachineLearningServices/workspaces/computes@2020-04-01#properties/properties/discriminator/mapping/AKS"
}
},
"HDInsight": {
"type": "object",
"metadata": {
"__bicep_resource_derived_type!": "Microsoft.MachineLearningServices/workspaces/computes@2020-04-01#properties/properties/discriminator/mapping/HDInsight"
}
},
"DataLakeAnalytics": {
"type": "object",
"metadata": {
"__bicep_resource_derived_type!": "Microsoft.MachineLearningServices/workspaces/computes@2020-04-01#properties/properties/discriminator/mapping/DataLakeAnalytics"
}
},
"foo": {
"type": "object",
"properties": {
"computeType": {
"type": "string",
"allowedValues": [
"foo"
]
},
"bar": {
"type": "string"
}
}
}
}
}
""");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -510,24 +510,6 @@ type discriminatedUnion1 = typeA | typeB
//@ }
//@ }
//@ },
//@ "a": {
//@ "$ref": "#/definitions/typeA"
//@ },
//@ "b": {
//@ "$ref": "#/definitions/typeB"
//@ },
//@ "a": {
//@ "$ref": "#/definitions/typeA"
//@ },
//@ "b": {
//@ "$ref": "#/definitions/typeB"
//@ },
//@ "a": {
//@ "$ref": "#/definitions/typeA"
//@ },
//@ "b": {
//@ "$ref": "#/definitions/typeB"
//@ },

@discriminator('type')
type discriminatedUnion2 = { type: 'c', value: string } | { type: 'd', value: bool }
Expand Down Expand Up @@ -659,6 +641,12 @@ type discriminatedUnion3 = discriminatedUnion1 | discriminatedUnion2 | { type: '
//@ "discriminator": {
//@ "propertyName": "type",
//@ "mapping": {
//@ "a": {
//@ "$ref": "#/definitions/typeA"
//@ },
//@ "b": {
//@ "$ref": "#/definitions/typeB"
//@ },
//@ "e": {
//@ "type": "object",
//@ "properties": {
Expand All @@ -684,6 +672,12 @@ type discriminatedUnion4 = discriminatedUnion1 | (discriminatedUnion2 | typeE)
//@ "discriminator": {
//@ "propertyName": "type",
//@ "mapping": {
//@ "a": {
//@ "$ref": "#/definitions/typeA"
//@ },
//@ "b": {
//@ "$ref": "#/definitions/typeB"
//@ },
//@ "e": {
//@ "$ref": "#/definitions/typeE"
//@ }
Expand Down Expand Up @@ -855,6 +849,12 @@ type inlineDiscriminatedUnion3 = {
//@ "discriminator": {
//@ "propertyName": "type",
//@ "mapping": {
//@ "a": {
//@ "$ref": "#/definitions/typeA"
//@ },
//@ "b": {
//@ "$ref": "#/definitions/typeB"
//@ },
//@ }
//@ }
//@ }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,28 @@ public void Supports_pointers_to_partial_resource_body_types()
ImmutableArray.Create<ITypeReference>(new ObjectType("dictionary",
TypeSymbolValidationFlags.Default,
ImmutableArray<TypeProperty>.Empty,
TypeFactory.CreateArrayType(targetType))),
TypeFactory.CreateArrayType(new DiscriminatedObjectType("taggedUnion",
TypeSymbolValidationFlags.Default,
"type",
ImmutableArray.Create<ITypeReference>(
new ObjectType("fooVariant",
TypeSymbolValidationFlags.Default,
ImmutableArray.Create(
new TypeProperty("type", TypeFactory.CreateStringLiteralType("foo")),
new TypeProperty("property", LanguageConstants.Int)),
null),
new ObjectType("barVariant",
TypeSymbolValidationFlags.Default,
ImmutableArray.Create(
new TypeProperty("type", TypeFactory.CreateStringLiteralType("bar")),
new TypeProperty("property", targetType)),
null)))))),
TypeSymbolValidationFlags.Default))),
null);
var (sut, unhydratedTypeRef) = SetupResolver(hydrated);

UnresolvedResourceDerivedType unresolved = new(unhydratedTypeRef,
ImmutableArray.Create("properties", "property", "prefixItems", "0", "additionalProperties", "items"),
ImmutableArray.Create("properties", "property", "prefixItems", "0", "additionalProperties", "items", "discriminator", "mapping", "bar", "properties", "property"),
LanguageConstants.Any);

var bound = sut.ResolveResourceDerivedTypes(unresolved);
Expand Down

0 comments on commit e0259b8

Please sign in to comment.