diff --git a/sdk/resourcemanager/Azure.ResourceManager.Core/src/Resources/PropertyReferenceTypeAttribute.cs b/sdk/resourcemanager/Azure.ResourceManager.Core/src/Resources/PropertyReferenceTypeAttribute.cs new file mode 100644 index 000000000000..f8a755b9aa9d --- /dev/null +++ b/sdk/resourcemanager/Azure.ResourceManager.Core/src/Resources/PropertyReferenceTypeAttribute.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Azure.ResourceManager.Core +{ + /// + /// An attribute class indicating a reference type for code generation. + /// + [AttributeUsage(AttributeTargets.Class)] + public class PropertyReferenceTypeAttribute : Attribute + { + /// + /// Instatiate a new reference type attribute. + /// + /// An array of types to skip for this reference type. + public PropertyReferenceTypeAttribute(Type[] skipTypes) + { + SkipTypes = skipTypes; + } + + /// + /// Instatiate a new reference type attribute. + /// + public PropertyReferenceTypeAttribute() : this(null) + { + } + + /// + /// Get an array of types to skip for this reference type. + /// + public Type[] SkipTypes { get; } + } +} diff --git a/sdk/resourcemanager/Azure.ResourceManager.Core/src/Resources/ResourceIdentity.cs b/sdk/resourcemanager/Azure.ResourceManager.Core/src/Resources/ResourceIdentity.cs index 9102f5db7b4d..12599e56aad6 100644 --- a/sdk/resourcemanager/Azure.ResourceManager.Core/src/Resources/ResourceIdentity.cs +++ b/sdk/resourcemanager/Azure.ResourceManager.Core/src/Resources/ResourceIdentity.cs @@ -11,6 +11,7 @@ namespace Azure.ResourceManager.Core /// /// Represents a managed identity /// + [PropertyReferenceType(new Type[] { typeof(ResourceIdentityType) })] public class ResourceIdentity : IEquatable { private const string SystemAssigned = "SystemAssigned"; @@ -20,6 +21,7 @@ public class ResourceIdentity : IEquatable /// /// Initializes a new instance of the class. /// + [InitializationConstructor] public ResourceIdentity() : this(null, false) { @@ -30,6 +32,7 @@ public ResourceIdentity() /// /// Dictionary with a key and a object value. /// Flag for using or not. + [SerializationConstructor] public ResourceIdentity(Dictionary user, bool useSystemAssigned) { // check for combination of user and system on the impact to type value @@ -78,7 +81,9 @@ public ResourceIdentity(SystemAssignedIdentity systemAssigned, IDictionary /// A containing an . /// New Identity object with JSON values. - internal static ResourceIdentity Deserialize(JsonElement element) +#pragma warning disable AZC0014 // Avoid using banned types in public API + public static ResourceIdentity DeserializeResourceIdentity(JsonElement element) +#pragma warning restore AZC0014 // Avoid using banned types in public API { if (element.ValueKind == JsonValueKind.Undefined) { diff --git a/sdk/resourcemanager/Azure.ResourceManager.Core/src/Resources/UserAssignedIdentity.cs b/sdk/resourcemanager/Azure.ResourceManager.Core/src/Resources/UserAssignedIdentity.cs index 3450deff2ff7..88ed8932bddf 100644 --- a/sdk/resourcemanager/Azure.ResourceManager.Core/src/Resources/UserAssignedIdentity.cs +++ b/sdk/resourcemanager/Azure.ResourceManager.Core/src/Resources/UserAssignedIdentity.cs @@ -26,12 +26,12 @@ public UserAssignedIdentity(Guid clientId, Guid principalId) /// /// Gets or sets the Client ID. /// - public Guid ClientId { get; set; } + public Guid ClientId { get; } /// /// Gets or sets the Principal ID. /// - public Guid PrincipalId { get; set; } + public Guid PrincipalId { get; } /// /// Converts a into an object. diff --git a/sdk/resourcemanager/Azure.ResourceManager.Core/tests/Unit/IdentityTests.cs b/sdk/resourcemanager/Azure.ResourceManager.Core/tests/Unit/IdentityTests.cs index a725b9130f75..88419932480c 100644 --- a/sdk/resourcemanager/Azure.ResourceManager.Core/tests/Unit/IdentityTests.cs +++ b/sdk/resourcemanager/Azure.ResourceManager.Core/tests/Unit/IdentityTests.cs @@ -151,7 +151,7 @@ public void EqualsTestFalseSameKey() public void TestDeserializerInvalidDefaultJson() { JsonElement invalid = default(JsonElement); - Assert.Throws(delegate { ResourceIdentity.Deserialize(invalid); }); + Assert.Throws(delegate { ResourceIdentity.DeserializeResourceIdentity(invalid); }); } public JsonProperty DeserializerHelper(string filename) @@ -166,14 +166,14 @@ public JsonProperty DeserializerHelper(string filename) public void TestDeserializerInvalidNullType() { var identityJsonProperty = DeserializerHelper("InvalidTypeIsNull.json"); - Assert.Throws(delegate { ResourceIdentity.Deserialize(identityJsonProperty.Value); }); + Assert.Throws(delegate { ResourceIdentity.DeserializeResourceIdentity(identityJsonProperty.Value); }); } [TestCase] public void TestDeserializerValidSystemAndUserAssigned() { var identityJsonProperty = DeserializerHelper("SystemAndUserAssignedValid.json"); - ResourceIdentity back = ResourceIdentity.Deserialize(identityJsonProperty.Value); + ResourceIdentity back = ResourceIdentity.DeserializeResourceIdentity(identityJsonProperty.Value); Assert.IsTrue("22fdaec1-8b9f-49dc-bd72-ddaf8f215577".Equals(back.SystemAssignedIdentity.PrincipalId.ToString())); Assert.IsTrue("72f988af-86f1-41af-91ab-2d7cd011db47".Equals(back.SystemAssignedIdentity.TenantId.ToString())); var user = back.UserAssignedIdentities; @@ -186,7 +186,7 @@ public void TestDeserializerValidSystemAndUserAssigned() public void TestDeserializerInvalidType() { var identityJsonProperty = DeserializerHelper("InvalidType.json"); - ResourceIdentity back = ResourceIdentity.Deserialize(identityJsonProperty.Value); + ResourceIdentity back = ResourceIdentity.DeserializeResourceIdentity(identityJsonProperty.Value); var user = back.UserAssignedIdentities; Assert.AreEqual("/subscriptions/d96407f5-db8f-4325-b582-84ad21310bd8/resourceGroups/tester/providers/Microsoft.ManagedIdentity/userAssignedIdentities/testidentity", user.Keys.First().ToString()); Assert.AreEqual("9a2eaa6a-b49c-4a63-afb5-3b72e3e65422", user.Values.First().ClientId.ToString()); @@ -197,7 +197,7 @@ public void TestDeserializerInvalidType() public void TestDeserializerValidInnerExtraField() { var identityJsonProperty = DeserializerHelper("SystemAndUserAssignedInnerExtraField.json"); - ResourceIdentity back = ResourceIdentity.Deserialize(identityJsonProperty.Value); + ResourceIdentity back = ResourceIdentity.DeserializeResourceIdentity(identityJsonProperty.Value); Assert.IsTrue("22fddec1-8b9f-49dc-bd72-ddaf8f215577".Equals(back.SystemAssignedIdentity.PrincipalId.ToString())); Assert.IsTrue("72f988bf-86f1-41af-91ab-2d7cd011db47".Equals(back.SystemAssignedIdentity.TenantId.ToString())); var user = back.UserAssignedIdentities; @@ -210,7 +210,7 @@ public void TestDeserializerValidInnerExtraField() public void TestDeserializerValidMiddleExtraField() { var identityJsonProperty = DeserializerHelper("SystemAndUserAssignedMiddleExtraField.json"); - ResourceIdentity back = ResourceIdentity.Deserialize(identityJsonProperty.Value); + ResourceIdentity back = ResourceIdentity.DeserializeResourceIdentity(identityJsonProperty.Value); Assert.IsTrue("22fddec1-8b9f-49dc-bd72-ddaf8f215577".Equals(back.SystemAssignedIdentity.PrincipalId.ToString())); Assert.IsTrue("72f988bf-86f1-41af-91ab-2d7cd011db47".Equals(back.SystemAssignedIdentity.TenantId.ToString())); var user = back.UserAssignedIdentities; @@ -226,7 +226,7 @@ public void TestDeserializerValidOuterExtraField() JsonDocument document = JsonDocument.Parse(json); JsonElement rootElement = document.RootElement; var identityJsonProperty = rootElement.EnumerateObject().ElementAt(1); - ResourceIdentity back = ResourceIdentity.Deserialize(identityJsonProperty.Value); + ResourceIdentity back = ResourceIdentity.DeserializeResourceIdentity(identityJsonProperty.Value); Assert.IsTrue("22fddec1-8b9f-49dc-bd72-ddaf8f215577".Equals(back.SystemAssignedIdentity.PrincipalId.ToString())); Assert.IsTrue("72f988bf-86f1-41af-91ab-2d7cd011db47".Equals(back.SystemAssignedIdentity.TenantId.ToString())); var user = back.UserAssignedIdentities; @@ -239,7 +239,7 @@ public void TestDeserializerValidOuterExtraField() public void TestDeserializerValidSystemAndMultUser() { var identityJsonProperty = DeserializerHelper("SystemAndUserAssignedValidMultIdentities.json"); - ResourceIdentity back = ResourceIdentity.Deserialize(identityJsonProperty.Value); + ResourceIdentity back = ResourceIdentity.DeserializeResourceIdentity(identityJsonProperty.Value); Assert.IsTrue("22fddec1-8b9f-49dc-bd72-ddaf8f215570".Equals(back.SystemAssignedIdentity.PrincipalId.ToString())); Assert.IsTrue("72f988bf-86f1-41af-91ab-2d7cd011db40".Equals(back.SystemAssignedIdentity.TenantId.ToString())); var user = back.UserAssignedIdentities; @@ -255,7 +255,7 @@ public void TestDeserializerValidSystemAndMultUser() public void TestDeserializerValidSystemAssigned() { var identityJsonProperty = DeserializerHelper("SystemAssigned.json"); - ResourceIdentity back = ResourceIdentity.Deserialize(identityJsonProperty.Value); + ResourceIdentity back = ResourceIdentity.DeserializeResourceIdentity(identityJsonProperty.Value); Assert.IsTrue("22fddec1-8b9f-49dc-bd72-ddaf8f215577".Equals(back.SystemAssignedIdentity.PrincipalId.ToString())); Assert.IsTrue("72f988bf-86f1-41af-91ab-2d7cd011db47".Equals(back.SystemAssignedIdentity.TenantId.ToString())); Assert.IsTrue(back.UserAssignedIdentities.Count == 0); @@ -265,7 +265,7 @@ public void TestDeserializerValidSystemAssigned() public void TestDeserializerValidUserAssigned() { var identityJsonProperty = DeserializerHelper("UserAssigned.json"); - ResourceIdentity back = ResourceIdentity.Deserialize(identityJsonProperty.Value); + ResourceIdentity back = ResourceIdentity.DeserializeResourceIdentity(identityJsonProperty.Value); Assert.IsNull(back.SystemAssignedIdentity); var user = back.UserAssignedIdentities; Assert.AreEqual("/subscriptions/db1ab6f0-4769-4b2e-930e-01e2ef9c123c/resourceGroups/tester-rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/testidentity", user.Keys.First().ToString()); diff --git a/sdk/resourcemanager/Azure.ResourceManager.Core/tests/Unit/PropertyReferenceTypeTests.cs b/sdk/resourcemanager/Azure.ResourceManager.Core/tests/Unit/PropertyReferenceTypeTests.cs new file mode 100644 index 000000000000..e78c36d909b7 --- /dev/null +++ b/sdk/resourcemanager/Azure.ResourceManager.Core/tests/Unit/PropertyReferenceTypeTests.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using NUnit.Framework; + +namespace Azure.ResourceManager.Core.Tests +{ + public class PropertyReferenceTypeTests + { + private static readonly Type PropertyReferenceTypeAttribute = typeof(PropertyReferenceTypeAttribute); + private static readonly Type SerializationConstructor = typeof(SerializationConstructorAttribute); + private static readonly Type InitializationConstructor = typeof(InitializationConstructorAttribute); + private static readonly IEnumerable AssemblyTypes = typeof(ArmClient).Assembly.GetTypes(); + + [Test] + public void ValidatePropertyReferenceTypeAttribute() + { + var type = typeof(PropertyReferenceTypeAttribute); + var fieldInfo = type.GetProperties(BindingFlags.Instance | BindingFlags.Public).FirstOrDefault(p => p.Name == "SkipTypes"); + Assert.NotNull(fieldInfo, $"Field 'SkipTypes' is not found"); + Assert.AreEqual(fieldInfo.PropertyType, typeof(Type[])); + Assert.True(fieldInfo.CanRead); + Assert.False(fieldInfo.CanWrite); + } + + [Test] + public void ValidateSerializationConstructor() + { + foreach (var refType in AssemblyTypes.Where(t => HasAttribute(t.GetCustomAttributes(false), PropertyReferenceTypeAttribute))) + { + var serializationCtor = refType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(c => HasAttribute(c.GetCustomAttributes(false), SerializationConstructor)).FirstOrDefault(); + Assert.IsNotNull(serializationCtor); + Assert.IsTrue(serializationCtor.IsPublic, $"Serialization ctor for {refType.Name} should not be public"); + } + } + + [Test] + public void ValidateInitializationConstructor() + { + foreach (var refType in AssemblyTypes.Where(t => HasAttribute(t.GetCustomAttributes(false), PropertyReferenceTypeAttribute))) + { + var initializationCtor = refType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Where(c => HasAttribute(c.GetCustomAttributes(false), InitializationConstructor)).FirstOrDefault(); + Assert.IsNotNull(initializationCtor); + Assert.IsTrue(refType.IsAbstract == initializationCtor.IsFamily, $"If {refType.Name} is abstract then its initialization ctor should be protected"); + Assert.IsTrue(refType.IsAbstract != initializationCtor.IsPublic, $"If {refType.Name} is abstract then its initialization ctor should be public"); + Assert.IsFalse(initializationCtor.IsAssembly, $"Initialization ctor for {refType.Name} should not be internal"); + } + } + + public bool HasAttribute(IEnumerable list, Type attributeType) + { + return list.FirstOrDefault(a => a.GetType() == attributeType) is not null; + } + } +}