Skip to content

Commit

Permalink
Add PropertyReferenceType attribute in ResourceIdentity (#22255)
Browse files Browse the repository at this point in the history
* Add PropertyReferenceTypeAttribute attribute in ResourceIdentity

* Feedback
  • Loading branch information
ShivangiReja authored Jun 29, 2021
1 parent 8fe07a7 commit ddf31a6
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// An attribute class indicating a reference type for code generation.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class PropertyReferenceTypeAttribute : Attribute
{
/// <summary>
/// Instatiate a new reference type attribute.
/// </summary>
/// <param name="skipTypes"> An array of types to skip for this reference type. </param>
public PropertyReferenceTypeAttribute(Type[] skipTypes)
{
SkipTypes = skipTypes;
}

/// <summary>
/// Instatiate a new reference type attribute.
/// </summary>
public PropertyReferenceTypeAttribute() : this(null)
{
}

/// <summary>
/// Get an array of types to skip for this reference type.
/// </summary>
public Type[] SkipTypes { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Azure.ResourceManager.Core
/// <summary>
/// Represents a managed identity
/// </summary>
[PropertyReferenceType(new Type[] { typeof(ResourceIdentityType) })]
public class ResourceIdentity : IEquatable<ResourceIdentity>
{
private const string SystemAssigned = "SystemAssigned";
Expand All @@ -20,6 +21,7 @@ public class ResourceIdentity : IEquatable<ResourceIdentity>
/// <summary>
/// Initializes a new instance of the <see cref="ResourceIdentity"/> class.
/// </summary>
[InitializationConstructor]
public ResourceIdentity()
: this(null, false)
{
Expand All @@ -30,6 +32,7 @@ public ResourceIdentity()
/// </summary>
/// <param name="user"> Dictionary with a <see cref="ResourceIdentifier"/> key and a <see cref="UserAssignedIdentity"/> object value. </param>
/// <param name="useSystemAssigned"> Flag for using <see cref="SystemAssignedIdentity"/> or not. </param>
[SerializationConstructor]
public ResourceIdentity(Dictionary<ResourceGroupResourceIdentifier, UserAssignedIdentity> user, bool useSystemAssigned)
{
// check for combination of user and system on the impact to type value
Expand Down Expand Up @@ -78,7 +81,9 @@ public ResourceIdentity(SystemAssignedIdentity systemAssigned, IDictionary<Resou
/// </summary>
/// <param name="element"> A <see cref="JsonElement"/> containing an <see cref="ResourceIdentity"/>. </param>
/// <returns> New Identity object with JSON values. </returns>
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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ public UserAssignedIdentity(Guid clientId, Guid principalId)
/// <summary>
/// Gets or sets the Client ID.
/// </summary>
public Guid ClientId { get; set; }
public Guid ClientId { get; }

/// <summary>
/// Gets or sets the Principal ID.
/// </summary>
public Guid PrincipalId { get; set; }
public Guid PrincipalId { get; }

/// <summary>
/// Converts a <see cref="JsonElement"/> into an <see cref="UserAssignedIdentity"/> object.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public void EqualsTestFalseSameKey()
public void TestDeserializerInvalidDefaultJson()
{
JsonElement invalid = default(JsonElement);
Assert.Throws<ArgumentException>(delegate { ResourceIdentity.Deserialize(invalid); });
Assert.Throws<ArgumentException>(delegate { ResourceIdentity.DeserializeResourceIdentity(invalid); });
}

public JsonProperty DeserializerHelper(string filename)
Expand All @@ -166,14 +166,14 @@ public JsonProperty DeserializerHelper(string filename)
public void TestDeserializerInvalidNullType()
{
var identityJsonProperty = DeserializerHelper("InvalidTypeIsNull.json");
Assert.Throws<InvalidOperationException>(delegate { ResourceIdentity.Deserialize(identityJsonProperty.Value); });
Assert.Throws<InvalidOperationException>(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;
Expand All @@ -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());
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Type> 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<Attribute>(false), PropertyReferenceTypeAttribute)))
{
var serializationCtor = refType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(c => HasAttribute(c.GetCustomAttributes<Attribute>(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<Attribute>(false), PropertyReferenceTypeAttribute)))
{
var initializationCtor = refType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(c => HasAttribute(c.GetCustomAttributes<Attribute>(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<Attribute> list, Type attributeType)
{
return list.FirstOrDefault(a => a.GetType() == attributeType) is not null;
}
}
}

0 comments on commit ddf31a6

Please sign in to comment.