diff --git a/AccordProject.Concerto.Tests/ConcertoUtilsTests.cs b/AccordProject.Concerto.Tests/ConcertoUtilsTests.cs index eb1acaf..10e7eee 100644 --- a/AccordProject.Concerto.Tests/ConcertoUtilsTests.cs +++ b/AccordProject.Concerto.Tests/ConcertoUtilsTests.cs @@ -104,4 +104,46 @@ public void CannotParseTypeWithEmptyVersion() var ex = Assert.Throws(() => ConcertoUtils.ParseType("org.example@.Foo")); Assert.Equal("Invalid fully qualified name \"org.example@.Foo\"", ex.Message); } + + [Fact] + public void HasIdentifierTrueForTypeWithIdentifier() + { + var employee = new Employee() { EmployeeId = "12345678" }; + Assert.True(ConcertoUtils.HasIdentifier(employee)); + } + + [Fact] + public void HasIdentifierTrueForTypeWithInheritedIdentifier() + { + var manager = new Manager() { EmployeeId = "12345678" }; + Assert.True(ConcertoUtils.HasIdentifier(manager)); + } + + [Fact] + public void HasIdentifierFalseForTypeWithoutIdentifier() + { + var project = new Project() {}; + Assert.False(ConcertoUtils.HasIdentifier(project)); + } + + [Fact] + public void GetIdentifierReturnsForTypeWithIdentifier() + { + var employee = new Employee() { EmployeeId = "12345678" }; + Assert.Equal("12345678", ConcertoUtils.GetIdentifier(employee)); + } + + [Fact] + public void GetIdentifierReturnsForTypeWithInheritedIdentifier() + { + var manager = new Manager() { EmployeeId = "12345678" }; + Assert.Equal("12345678", ConcertoUtils.GetIdentifier(manager)); + } + + [Fact] + public void GetIdentifierReturnsForTypeWithoutIdentifier() + { + var project = new Project() {}; + Assert.Null(ConcertoUtils.GetIdentifier(project)); + } } \ No newline at end of file diff --git a/AccordProject.Concerto.Tests/TestTypes.cs b/AccordProject.Concerto.Tests/TestTypes.cs index c78aae9..e9ee668 100644 --- a/AccordProject.Concerto.Tests/TestTypes.cs +++ b/AccordProject.Concerto.Tests/TestTypes.cs @@ -42,6 +42,7 @@ public class Employee : Person { public Department? Department { get; set; } [Newtonsoft.Json.JsonProperty("manager")] public Employee Manager { get; set; } + [AccordProject.Concerto.Identifier()] [Newtonsoft.Json.JsonProperty("employeeId")] public string EmployeeId { get; set; } } @@ -53,3 +54,9 @@ public class Manager : Employee { [Newtonsoft.Json.JsonProperty("budget")] public float Budget { get; set; } } +[AccordProject.Concerto.Type(Namespace = "org.accordproject.concerto.test", Version = "1.2.3", Name = "Project")] +[Newtonsoft.Json.JsonConverter(typeof(AccordProject.Concerto.ConcertoConverterNewtonsoft))] +public class Project : Concept { + [Newtonsoft.Json.JsonProperty("$class")] + public override string _Class { get; } = "org.accordproject.concerto.test@1.2.3.Project"; +} diff --git a/AccordProject.Concerto.Tests/data/employee.cto b/AccordProject.Concerto.Tests/data/employee.cto index b96bd4a..b98e155 100644 --- a/AccordProject.Concerto.Tests/data/employee.cto +++ b/AccordProject.Concerto.Tests/data/employee.cto @@ -37,3 +37,7 @@ participant Employee identified by employeeId extends Person { participant Manager extends Employee { o Double budget optional } + +concept Project { + +} \ No newline at end of file diff --git a/AccordProject.Concerto/ConcertoAttributes.cs b/AccordProject.Concerto/ConcertoAttributes.cs index 4eb41ce..87f0847 100644 --- a/AccordProject.Concerto/ConcertoAttributes.cs +++ b/AccordProject.Concerto/ConcertoAttributes.cs @@ -15,7 +15,8 @@ namespace AccordProject.Concerto; [AttributeUsage(AttributeTargets.Class)] -public class TypeAttribute : Attribute { +public class TypeAttribute : Attribute +{ public string Namespace; public string? Version; public string Name; @@ -29,3 +30,9 @@ public ConcertoType ToType() }; } } + +[AttributeUsage(AttributeTargets.Property)] +public class IdentifierAttribute : Attribute +{ + +} \ No newline at end of file diff --git a/AccordProject.Concerto/ConcertoTypes.cs b/AccordProject.Concerto/ConcertoTypes.cs index 1a15333..b94e63e 100644 --- a/AccordProject.Concerto/ConcertoTypes.cs +++ b/AccordProject.Concerto/ConcertoTypes.cs @@ -24,6 +24,7 @@ public abstract class Concept { public abstract class Asset : Concept { [Newtonsoft.Json.JsonProperty("$class")] public override string _Class { get; } = "concerto@1.0.0.Asset"; + [AccordProject.Concerto.Identifier()] [Newtonsoft.Json.JsonProperty("$identifier")] public string _Identifier { get; set; } } @@ -32,6 +33,7 @@ public abstract class Asset : Concept { public abstract class Participant : Concept { [Newtonsoft.Json.JsonProperty("$class")] public override string _Class { get; } = "concerto@1.0.0.Participant"; + [AccordProject.Concerto.Identifier()] [Newtonsoft.Json.JsonProperty("$identifier")] public string _Identifier { get; set; } } diff --git a/AccordProject.Concerto/ConcertoUtils.cs b/AccordProject.Concerto/ConcertoUtils.cs index 827d6dc..a18da22 100644 --- a/AccordProject.Concerto/ConcertoUtils.cs +++ b/AccordProject.Concerto/ConcertoUtils.cs @@ -12,6 +12,10 @@ * limitations under the License. */ +namespace AccordProject.Concerto; + +using System.Reflection; + /// /// This class represents a Concerto type, for example org.example@1.2.3.Foo. /// @@ -107,4 +111,38 @@ public static ConcertoType ParseType(string fqn) } return new ConcertoType() { Namespace = ns, Version = version, Name = name }; } + + public static bool HasIdentifier(Concept concept) + { + var type = concept.GetType(); + var identifierProperty = FindIdentifierProperty(type); + return identifierProperty is not null; + } + + public static string? GetIdentifier(Concept concept) + { + var type = concept.GetType(); + var identifierProperty = FindIdentifierProperty(type); + if (identifierProperty is null) + { + return null; + } + return identifierProperty.GetValue(concept) as string; + } + + private static PropertyInfo? FindIdentifierProperty(Type type) + { + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + var identifierProperty = properties.FirstOrDefault(property => property.GetCustomAttributes(typeof(IdentifierAttribute), false).Length > 0); + if (identifierProperty is not null) + { + return identifierProperty; + } + var baseType = type.BaseType; + if (baseType is null) + { + return null; + } + return FindIdentifierProperty(baseType); + } } \ No newline at end of file