forked from Azure/bicep
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
In progress for Azure#1363 This change add support for lexical scoping/nesting of child resources. That it, for a resource type: `My.RP/someType@2020-01-01` there's now a simplified syntax for including a resource: `My.RP/someType/childType@2020-01-01` inside the body of the parent. This syntax also allows simplication of the type name of the child, limits its scope/visibility and implies an automatic `dependsOn` to the parent resource. The goal is to be the most idiomatic way to declare multiple resources to be deployed with a parent/child/grandchild/etc relationship. This is a mostly feature complete implementation of the proposal at issue Azure#1363.
- Loading branch information
Showing
41 changed files
with
1,337 additions
and
179 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
using System.Linq; | ||
using Bicep.Core.Diagnostics; | ||
using Bicep.Core.Semantics; | ||
using Bicep.Core.TypeSystem; | ||
using Bicep.Core.UnitTests.Assertions; | ||
using Bicep.Core.UnitTests.Utils; | ||
using FluentAssertions; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
||
namespace Bicep.Core.IntegrationTests | ||
{ | ||
[TestClass] | ||
public class NestedResourceTests | ||
{ | ||
[TestMethod] | ||
public void NestedResources_symbols_are_bound() | ||
{ | ||
var program = @" | ||
resource parent 'My.RP/parentType@2020-01-01' = { | ||
name: 'parent' | ||
properties: { | ||
size: 'large' | ||
} | ||
resource child 'childType' = { | ||
name: 'child' | ||
properties: { | ||
style: 'very cool' | ||
} | ||
} | ||
resource sibling 'childType@2020-01-02' = { | ||
name: 'sibling' | ||
properties: { | ||
style: child.properties.style | ||
size: parent.properties.size | ||
} | ||
} | ||
} | ||
"; | ||
|
||
var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program)); | ||
var model = compilation.GetEntrypointSemanticModel(); | ||
|
||
model.GetAllDiagnostics().Should().BeEmpty(); | ||
|
||
var expected = new [] | ||
{ | ||
new { name = "child", type = "My.RP/parentType/childType@2020-01-01", }, | ||
new { name = "parent", type = "My.RP/parentType@2020-01-01", }, | ||
new { name = "sibling", type = "My.RP/parentType/childType@2020-01-02", }, | ||
}; | ||
|
||
model.Root.GetAllResourceDeclarations() | ||
.Select(s => new { name = s.Name, type = (s.Type as ResourceType)?.TypeReference.FormatName(), }) | ||
.OrderBy(n => n.name) | ||
.Should().BeEquivalentTo(expected); | ||
} | ||
|
||
[TestMethod] | ||
public void NestedResources_child_cannot_be_referenced_outside_of_scope() | ||
{ | ||
var program = @" | ||
resource parent 'My.RP/parentType@2020-01-01' = { | ||
name: 'parent' | ||
properties: { | ||
} | ||
resource child 'childType' = { | ||
name: 'child' | ||
properties: { | ||
style: 'very cool' | ||
} | ||
} | ||
} | ||
resource other 'My.RP/parentType@2020-01-01' = { | ||
name: 'other' | ||
properties: { | ||
style: child.properties.style | ||
} | ||
} | ||
"; | ||
|
||
var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program)); | ||
var diagnostics = compilation.GetEntrypointSemanticModel().GetAllDiagnostics(); | ||
diagnostics.Should().HaveDiagnostics(new[] { | ||
("BCP057", DiagnosticLevel.Error, "The name \"child\" does not exist in the current context."), | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void NestedResources_child_cannot_specify_qualified_type() | ||
{ | ||
var program = @" | ||
resource parent 'My.RP/parentType@2020-01-01' = { | ||
name: 'parent' | ||
properties: { | ||
} | ||
resource child 'My.RP/parentType/childType@2020-01-01' = { | ||
name: 'child' | ||
properties: { | ||
style: 'very cool' | ||
} | ||
} | ||
} | ||
"; | ||
|
||
var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program)); | ||
var diagnostics = compilation.GetEntrypointSemanticModel().GetAllDiagnostics(); | ||
diagnostics.Should().HaveDiagnostics(new[] { | ||
("BCP139", DiagnosticLevel.Error, "The resource type part \"My.RP/parentType/childType@2020-01-01\" is invalid. Nested resources must specify a single type, and optionally can specify a version using the format \"<type>@<apiVersion>\"."), | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void NestedResources_error_in_base_type() | ||
{ | ||
var program = @" | ||
resource parent 'My.RP/parentType@invalid-version' = { | ||
name: 'parent' | ||
properties: { | ||
} | ||
resource child 'My.RP/parentType/childType@2020-01-01' = { | ||
name: 'child' | ||
properties: { | ||
style: 'very cool' | ||
} | ||
} | ||
} | ||
"; | ||
|
||
var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program)); | ||
var diagnostics = compilation.GetEntrypointSemanticModel().GetAllDiagnostics(); | ||
diagnostics.Should().HaveDiagnostics(new[] { | ||
("BCP029", DiagnosticLevel.Error, "The resource type is not valid. Specify a valid resource type of format \"<provider>/<types>@<apiVersion>\"."), | ||
("BCP140", DiagnosticLevel.Error, "The resource type cannot be determined due an error in containing resource \"parent\"."), | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void NestedResources_error_in_parent_type() | ||
{ | ||
var program = @" | ||
resource parent 'My.RP/parentType@2020-01-01' = { | ||
name: 'parent' | ||
properties: { | ||
} | ||
// Error here | ||
resource child 'My.RP/parentType/childType@2020-01-01' = { | ||
name: 'child' | ||
properties: { | ||
} | ||
resource grandchild 'granchildType' = { | ||
name: 'grandchild' | ||
properties: { | ||
} | ||
} | ||
} | ||
} | ||
"; | ||
|
||
var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program)); | ||
var diagnostics = compilation.GetEntrypointSemanticModel().GetAllDiagnostics(); | ||
diagnostics.Should().HaveDiagnostics(new[] { | ||
("BCP139", DiagnosticLevel.Error, "The resource type part \"My.RP/parentType/childType@2020-01-01\" is invalid. Nested resources must specify a single type, and optionally can specify a version using the format \"<type>@<apiVersion>\"."), | ||
("BCP140", DiagnosticLevel.Error, "The resource type cannot be determined due an error in containing resource \"child\"."), | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void NestedResources_child_cycle_is_detected_correctly() | ||
{ | ||
var program = @" | ||
resource parent 'My.RP/parentType@2020-01-01' = { | ||
name: 'parent' | ||
properties: { | ||
style: child.properties.style | ||
} | ||
resource child 'childType' = { | ||
name: 'child' | ||
properties: { | ||
style: 'very cool' | ||
} | ||
} | ||
} | ||
"; | ||
|
||
var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program)); | ||
compilation.GetEntrypointSemanticModel().GetAllDiagnostics().Should().HaveDiagnostics(new[] { | ||
("BCP080", DiagnosticLevel.Error, "The expression is involved in a cycle (\"child\" -> \"parent\")."), | ||
}); | ||
} | ||
|
||
[TestMethod] // With more than one level of nesting the name just isn't visible. | ||
public void NestedResources_grandchild_cycle_results_in_binding_failure() | ||
{ | ||
var program = @" | ||
resource parent 'My.RP/parentType@2020-01-01' = { | ||
name: 'parent' | ||
properties: { | ||
style: grandchild.properties.style | ||
} | ||
resource child 'childType' = { | ||
name: 'child' | ||
properties: { | ||
} | ||
resource grandchild 'grandchildType' = { | ||
name: 'grandchild' | ||
properties: { | ||
style: 'very cool' | ||
} | ||
} | ||
} | ||
} | ||
"; | ||
|
||
var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program)); | ||
compilation.GetEntrypointSemanticModel().GetAllDiagnostics().Should().HaveDiagnostics(new[] { | ||
("BCP057", DiagnosticLevel.Error, "The name \"grandchild\" does not exist in the current context."), | ||
}); | ||
} | ||
|
||
[TestMethod] | ||
public void NestedResources_ancestors_are_detected() | ||
{ | ||
var program = @" | ||
resource parent 'My.RP/parentType@2020-01-01' = { | ||
name: 'parent' | ||
properties: { | ||
} | ||
resource child 'childType' = { | ||
name: 'child' | ||
properties: { | ||
} | ||
resource grandchild 'grandchildType' = { | ||
name: 'grandchild' | ||
properties: { | ||
} | ||
} | ||
} | ||
} | ||
"; | ||
|
||
var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program)); | ||
var model = compilation.GetEntrypointSemanticModel(); | ||
model.GetAllDiagnostics().Should().BeEmpty(); | ||
|
||
var parent = model.Root.GetAllResourceDeclarations().Single(r => r.Name == "parent"); | ||
model.ResourceAncestors.GetAncestors(parent).Should().BeEmpty(); | ||
|
||
var child = model.Root.GetAllResourceDeclarations().Single(r => r.Name == "child"); | ||
model.ResourceAncestors.GetAncestors(child).Should().Equal(new []{ parent, }); | ||
|
||
var grandchild = model.Root.GetAllResourceDeclarations().Single(r => r.Name == "grandchild"); | ||
model.ResourceAncestors.GetAncestors(grandchild).Should().Equal(new []{ parent, child, }); // order matters | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.