Skip to content

Commit

Permalink
Add support for name discriminators on resource types (#1950)
Browse files Browse the repository at this point in the history
* Add support for name discriminators on resource types

* Add tests

* Pull in types 0.1.95

* Fix VSCode e2e tests
  • Loading branch information
anthony-c-martin authored Mar 22, 2021
1 parent 9d091e0 commit 291ca0f
Show file tree
Hide file tree
Showing 28 changed files with 6,200 additions and 5,245 deletions.
1 change: 0 additions & 1 deletion docs/examples/101/function-app-create/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ resource functionAppConfig 'Microsoft.Web/sites/config@2020-06-01' = {
ftpsState: 'AllAllowed'
PreWarmedInstanceCount: 0
}
tags: appTags
}

// Function App Binding
Expand Down
3 changes: 1 addition & 2 deletions docs/examples/101/function-app-create/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@
"ftpsState": "AllAllowed",
"PreWarmedInstanceCount": 0
},
"tags": "[variables('appTags')]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('functionAppName'))]"
]
Expand All @@ -280,7 +279,7 @@
"_generator": {
"name": "bicep",
"version": "dev",
"templateHash": "11553591864062128932"
"templateHash": "5839598444193799735"
}
}
}
10 changes: 6 additions & 4 deletions docs/examples/201/sql/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ resource sqlServer 'Microsoft.Sql/servers@2020-02-02-preview' = {
}

resource db 'Microsoft.Sql/servers/databases@2020-02-02-preview' = {
name: '${sqlServer.name}/${databaseName}' // originally using sqlServerName param, but dependsOn was not automatically added
parent: sqlServer
name: databaseName
location: location
sku: {
name: databaseServiceObjectiveName
Expand All @@ -41,9 +42,10 @@ resource db 'Microsoft.Sql/servers/databases@2020-02-02-preview' = {
}

// very long type...
resource tde 'Microsoft.Sql/servers/databases/transparentDataEncryption@2017-03-01-preview' = {
name: '${db.name}/current' // had to change databaseName => db.name to get dependsOn working
resource tde 'Microsoft.Sql/servers/databases/transparentDataEncryption@2020-08-01-preview' = {
parent: db
name: 'current' // had to change databaseName => db.name to get dependsOn working
properties: {
status: transparentDataEncryption
state: transparentDataEncryption
}
}
11 changes: 6 additions & 5 deletions docs/examples/201/sql/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,22 @@
},
{
"type": "Microsoft.Sql/servers/databases/transparentDataEncryption",
"apiVersion": "2017-03-01-preview",
"name": "[format('{0}/current', format('{0}/{1}', variables('sqlServerName'), variables('databaseName')))]",
"apiVersion": "2020-08-01-preview",
"name": "[format('{0}/{1}/{2}', variables('sqlServerName'), variables('databaseName'), 'current')]",
"properties": {
"status": "[parameters('transparentDataEncryption')]"
"state": "[parameters('transparentDataEncryption')]"
},
"dependsOn": [
"[resourceId('Microsoft.Sql/servers/databases', split(format('{0}/{1}', variables('sqlServerName'), variables('databaseName')), '/')[0], split(format('{0}/{1}', variables('sqlServerName'), variables('databaseName')), '/')[1])]"
"[resourceId('Microsoft.Sql/servers/databases', variables('sqlServerName'), variables('databaseName'))]",
"[resourceId('Microsoft.Sql/servers', variables('sqlServerName'))]"
]
}
],
"metadata": {
"_generator": {
"name": "bicep",
"version": "dev",
"templateHash": "4831708696527294286"
"templateHash": "4784448864382167112"
}
}
}
187 changes: 187 additions & 0 deletions src/Bicep.Core.IntegrationTests/ScenarioTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
using System.ComponentModel.DataAnnotations;
using Bicep.Core.Semantics;
using System.Linq;
using Bicep.Core.TypeSystem;
using Bicep.Core.Resources;

namespace Bicep.Core.IntegrationTests
{
Expand Down Expand Up @@ -1113,5 +1115,190 @@ param location string
diags.Should().BeEmpty();
}
}

[TestMethod]
public void Test_Issue657_discriminators()
{
var customTypes = new [] {
new ResourceType(
ResourceTypeReference.Parse("Rp.A/parent@2020-10-01"),
ResourceScope.ResourceGroup,
ResourceTypeProviderHelper.CreateObjectType(
"Rp.A/parent@2020-10-01",
("name", LanguageConstants.String))),
new ResourceType(
ResourceTypeReference.Parse("Rp.A/parent/child@2020-10-01"),
ResourceScope.ResourceGroup,
ResourceTypeProviderHelper.CreateDiscriminatedObjectType(
"Rp.A/parent/child@2020-10-01",
"name",
ResourceTypeProviderHelper.CreateObjectType(
"Val1Type",
("name", new StringLiteralType("val1")),
("properties", ResourceTypeProviderHelper.CreateObjectType(
"properties",
("onlyOnVal1", LanguageConstants.Bool)))),
ResourceTypeProviderHelper.CreateObjectType(
"Val2Type",
("name", new StringLiteralType("val2")),
("properties", ResourceTypeProviderHelper.CreateObjectType(
"properties",
("onlyOnVal2", LanguageConstants.Bool)))))),
};

var result = CompilationHelper.Compile(
ResourceTypeProviderHelper.CreateMockTypeProvider(customTypes),
("main.bicep", @"
resource test 'Rp.A/parent@2020-10-01' = {
name: 'test'
}
// top-level resource
resource test2 'Rp.A/parent/child@2020-10-01' = {
name: 'test/test2'
properties: {
anythingGoesHere: true
}
}
// 'existing' top-level resource
resource test3 'Rp.A/parent/child@2020-10-01' existing = {
name: 'test/test3'
}
// parent-property child resource
resource test4 'Rp.A/parent/child@2020-10-01' = {
parent: test
name: 'val1'
properties: {
onlyOnVal1: true
}
}
// 'existing' parent-property child resource
resource test5 'Rp.A/parent/child@2020-10-01' existing = {
parent: test
name: 'val2'
}
"));

result.Should().NotHaveDiagnostics();

var failedResult = CompilationHelper.Compile(
ResourceTypeProviderHelper.CreateMockTypeProvider(customTypes),
("main.bicep", @"
resource test 'Rp.A/parent@2020-10-01' = {
name: 'test'
}
// parent-property child resource
resource test4 'Rp.A/parent/child@2020-10-01' = {
parent: test
name: 'notAValidVal'
properties: {
onlyOnEnum: true
}
}
// 'existing' parent-property child resource
resource test5 'Rp.A/parent/child@2020-10-01' existing = {
parent: test
name: 'notAValidVal'
}
"));

failedResult.Should().HaveDiagnostics(new[] {
("BCP036", DiagnosticLevel.Error, "The property \"name\" expected a value of type \"'val1' | 'val2'\" but the provided value is of type \"'notAValidVal'\"."),
("BCP036", DiagnosticLevel.Error, "The property \"name\" expected a value of type \"'val1' | 'val2'\" but the provided value is of type \"'notAValidVal'\"."),
});
}

[TestMethod]
public void Test_Issue657_enum()
{
var customTypes = new [] {
new ResourceType(
ResourceTypeReference.Parse("Rp.A/parent@2020-10-01"),
ResourceScope.ResourceGroup,
ResourceTypeProviderHelper.CreateObjectType(
"Rp.A/parent@2020-10-01",
("name", LanguageConstants.String))),
new ResourceType(
ResourceTypeReference.Parse("Rp.A/parent/child@2020-10-01"),
ResourceScope.ResourceGroup,
ResourceTypeProviderHelper.CreateObjectType(
"Rp.A/parent/child@2020-10-01",
("name", UnionType.Create(new StringLiteralType("val1"), new StringLiteralType("val2"))),
("properties", ResourceTypeProviderHelper.CreateObjectType(
"properties",
("onlyOnEnum", LanguageConstants.Bool))))),
};

var result = CompilationHelper.Compile(
ResourceTypeProviderHelper.CreateMockTypeProvider(customTypes),
("main.bicep", @"
resource test 'Rp.A/parent@2020-10-01' = {
name: 'test'
}
// top-level resource
resource test2 'Rp.A/parent/child@2020-10-01' = {
name: 'test/test2'
properties: {
onlyOnEnum: true
}
}
// 'existing' top-level resource
resource test3 'Rp.A/parent/child@2020-10-01' existing = {
name: 'test/test3'
}
// parent-property child resource
resource test4 'Rp.A/parent/child@2020-10-01' = {
parent: test
name: 'val1'
properties: {
onlyOnEnum: true
}
}
// 'existing' parent-property child resource
resource test5 'Rp.A/parent/child@2020-10-01' existing = {
parent: test
name: 'val2'
}
"));

result.Should().NotHaveDiagnostics();

var failedResult = CompilationHelper.Compile(
ResourceTypeProviderHelper.CreateMockTypeProvider(customTypes),
("main.bicep", @"
resource test 'Rp.A/parent@2020-10-01' = {
name: 'test'
}
// parent-property child resource
resource test4 'Rp.A/parent/child@2020-10-01' = {
parent: test
name: 'notAValidVal'
properties: {
onlyOnEnum: true
}
}
// 'existing' parent-property child resource
resource test5 'Rp.A/parent/child@2020-10-01' existing = {
parent: test
name: 'notAValidVal'
}
"));

failedResult.Should().HaveDiagnostics(new[] {
("BCP036", DiagnosticLevel.Error, "The property \"name\" expected a value of type \"'val1' | 'val2'\" but the provided value is of type \"'notAValidVal'\"."),
("BCP036", DiagnosticLevel.Error, "The property \"name\" expected a value of type \"'val1' | 'val2'\" but the provided value is of type \"'notAValidVal'\"."),
});
}
}
}
26 changes: 12 additions & 14 deletions src/Bicep.Core.Samples/ExamplesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,8 @@ private static bool IsPermittedMissingTypeDiagnostic(Diagnostic diagnostic)

var permittedMissingTypeDiagnostics = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Resource type \"Microsoft.AppConfiguration/configurationStores/keyValues@2020-07-01-preview\" does not have types available.",
"Resource type \"Microsoft.AppConfiguration/configurationStores@2020-07-01-preview\" does not have types available.",
"Resource type \"microsoft.network/networkSecurityGroups@2020-08-01\" does not have types available.",
"Resource type \"Microsoft.Sql/servers/databases/transparentDataEncryption@2017-03-01-preview\" does not have types available.",
"Resource type \"Microsoft.Sql/servers@2020-02-02-preview\" does not have types available.",
"Resource type \"Microsoft.Web/sites/config@2020-06-01\" does not have types available.",
"Resource type \"Microsoft.Web/sites/siteextensions@2020-06-01\" does not have types available.",
// To exclude a particular type for BCP081 (if there are missing types), add an entry of format:
// "Resource type \"<type>\" does not have types available.",
};

return permittedMissingTypeDiagnostics.Contains(diagnostic.Message);
Expand All @@ -126,16 +121,19 @@ public void ExampleIsValid(ExampleData example)
var compilation = new Compilation(new AzResourceTypeProvider(), syntaxTreeGrouping);
var emitter = new TemplateEmitter(compilation.GetEntrypointSemanticModel(), BicepTestConstants.DevAssemblyFileVersion);

foreach (var (syntaxTree, diagnostics) in compilation.GetAllDiagnosticsBySyntaxTree())
{
DiagnosticAssertions.DoWithDiagnosticAnnotations(
syntaxTree,
diagnostics.Where(x => !IsPermittedMissingTypeDiagnostic(x)),
diagnostics => {
diagnostics.Should().BeEmpty("{0} should not have warnings or errors", syntaxTree.FileUri.LocalPath);
});
}

// group assertion failures using AssertionScope, rather than reporting the first failure
using (new AssertionScope())
{
foreach (var (syntaxTree, diagnostics) in compilation.GetAllDiagnosticsBySyntaxTree())
{
var nonPermittedDiagnostics = diagnostics.Where(x => !IsPermittedMissingTypeDiagnostic(x));

nonPermittedDiagnostics.Should().BeEmpty($"\"{syntaxTree.FileUri.LocalPath}\" should not have warnings or errors");
}

var exampleExists = File.Exists(jsonFileName);
exampleExists.Should().BeTrue($"Generated example \"{jsonFileName}\" should be checked in");

Expand Down
Loading

0 comments on commit 291ca0f

Please sign in to comment.