Skip to content

Commit 8096736

Browse files
committed
refactored schema validation using rule pattern
1 parent 876f1ac commit 8096736

File tree

13 files changed

+305
-107
lines changed

13 files changed

+305
-107
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Cocona;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Commands;
3+
4+
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import
5+
{
6+
public static class CoconaAppExtensions
7+
{
8+
public static CoconaApp UseImportFeature(this CoconaApp app)
9+
{
10+
app.AddCommands<ImportCommands>();
11+
return app;
12+
}
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators;
3+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules.EntitySchemas;
4+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules.EntitySchemas.FieldSchemas;
5+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules.EntitySchemas.RelationshipSchemas;
6+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.ValueConverters;
7+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Xrm.Sdk;
10+
using System.Reflection;
11+
12+
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import
13+
{
14+
public static class IServiceCollectionExtensions
15+
{
16+
public static IServiceCollection AddImportFeature(this IServiceCollection services)
17+
{
18+
19+
return services.RegisterFromReflection<IFieldSchemaValidationRule>()
20+
.RegisterFromReflection<IRelationshipSchemaValidationRule>()
21+
.AddSingleton<IDataverseValueConverter, DataverseValueConverter>(_ =>
22+
{
23+
var testtype = typeof(BaseValueConverter<EntityReference>);
24+
var testtype2 = typeof(EntityReferenceValueConverter);
25+
var types = Assembly.GetExecutingAssembly().GetTypes().Where(t => !t.IsAbstract &&
26+
!t.IsInterface && t.BaseType != null && t.BaseType.IsConstructedGenericType && t.BaseType.GetGenericTypeDefinition() == typeof(BaseValueConverter<>)).ToList();
27+
return new DataverseValueConverter(types);
28+
})
29+
.AddTransient<IValidator<ImportSchema>, SchemaValidator>()
30+
.AddTransient<IValidator<EntitySchema>, EntitySchemaValidator>()
31+
.AddSingleton<IImportTaskProcessorService, ImportTaskProcessorService>();
32+
}
33+
public static IServiceCollection RegisterFromReflection<T>(this IServiceCollection services)
34+
{
35+
var genericType = typeof(T);
36+
var classes = Assembly.GetCallingAssembly().GetTypes()
37+
.Where(type => genericType.IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract);
38+
foreach (var c in classes)
39+
services.AddTransient(genericType, c);
40+
return services;
41+
}
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules.EntitySchemas.FieldSchemas;
3+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules.EntitySchemas.RelationshipSchemas;
4+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared;
5+
using Microsoft.Xrm.Sdk.Metadata;
6+
7+
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules.EntitySchemas
8+
{
9+
public class EntitySchemaValidator : IValidator<EntitySchema>
10+
{
11+
private readonly IMetadataService _metadataService;
12+
private readonly IReadOnlyCollection<IFieldSchemaValidationRule> _fieldSchemaRules;
13+
private readonly IReadOnlyCollection<IRelationshipSchemaValidationRule> _relationshipSchemaRules;
14+
public EntitySchemaValidator(IMetadataService metadataService,
15+
IEnumerable<IFieldSchemaValidationRule> fieldSchemaRules,
16+
IEnumerable<IRelationshipSchemaValidationRule> relationshipSchemaRules)
17+
{
18+
_metadataService = metadataService;
19+
_fieldSchemaRules = fieldSchemaRules.ToArray();
20+
_relationshipSchemaRules = relationshipSchemaRules.ToArray();
21+
}
22+
23+
public async Task<ValidationResult> Validate(EntitySchema value)
24+
{
25+
var failures = new List<ValidationFailure>();
26+
var Result = new ValidationResult
27+
{
28+
Failures = failures
29+
};
30+
var entityMd = await _metadataService.GetEntity(value.Name);
31+
if (entityMd == null)
32+
{
33+
failures.Add(new ValidationFailure
34+
{
35+
Message = $"Entity {value.Name} does not exist in target environment"
36+
});
37+
return Result;
38+
}
39+
foreach (var fieldSchema in value?.Fields?.Field)
40+
{
41+
var fieldResult = await ValidateFieldSchema(fieldSchema, entityMd);
42+
if (!fieldResult.IsSuccess)
43+
{
44+
failures.Add(new ValidationFailure
45+
{
46+
Message = fieldResult.ErrorMessage,
47+
PropertyBound = $"{value.Name}.{fieldSchema.Name}"
48+
});
49+
}
50+
}
51+
52+
foreach (var relationshipSchema in value?.Relationships?.Relationship)
53+
{
54+
var relationshipResult = await ValidateRelationshipSchema(relationshipSchema, entityMd);
55+
if (!relationshipResult.IsSuccess)
56+
{
57+
failures.Add(new ValidationFailure
58+
{
59+
Message = relationshipResult.ErrorMessage,
60+
PropertyBound = $"{value.Name}.{relationshipSchema.Name}"
61+
});
62+
}
63+
}
64+
65+
return Result;
66+
}
67+
private async Task<RuleResult> ValidateFieldSchema(FieldSchema fieldSchema, EntityMetadata entityMetadata)
68+
{
69+
var attributeMetadata = entityMetadata.Attributes.FirstOrDefault(amd => amd.LogicalName == fieldSchema.Name);
70+
if (attributeMetadata == null)
71+
{
72+
return RuleResult.Failure($"Attribute '{fieldSchema.Name}' for Entity {entityMetadata.LogicalName} does not have exist in target environment");
73+
}
74+
foreach (var fieldSchemaRule in _fieldSchemaRules)
75+
{
76+
77+
var fieldValidationResult = await fieldSchemaRule.Validate(fieldSchema, attributeMetadata);
78+
if (!fieldValidationResult.IsSuccess)
79+
{
80+
return fieldValidationResult;
81+
}
82+
}
83+
return RuleResult.Success();
84+
}
85+
private async Task<RuleResult> ValidateRelationshipSchema(RelationshipSchema relationshipSchema, EntityMetadata entityMetadata)
86+
{
87+
var relationShipMetadata = await _metadataService.GetRelationShipM2M(relationshipSchema.Name);
88+
if (relationShipMetadata == null)
89+
{
90+
return RuleResult.Failure($"ManyToMany Relationship Table {relationshipSchema.Name} does not exist in target environment or it's not a M2M relationship.");
91+
}
92+
foreach (var schemaRule in _relationshipSchemaRules)
93+
{
94+
95+
var fieldValidationResult = await schemaRule.Validate(entityMetadata.LogicalName, relationshipSchema, relationShipMetadata);
96+
if (!fieldValidationResult.IsSuccess)
97+
{
98+
return fieldValidationResult;
99+
}
100+
}
101+
return RuleResult.Success();
102+
}
103+
}
104+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Mappers;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
3+
using Dataverse.ConfigurationMigrationTool.Console.Features.Shared;
4+
using Microsoft.Xrm.Sdk.Metadata;
5+
6+
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules.EntitySchemas.FieldSchemas
7+
{
8+
public class FieldTypeMustMatchWithAttributeValidationRule : IFieldSchemaValidationRule
9+
{
10+
private static IMapper<FieldSchema, AttributeTypeCode?> AttributeTypeMapper = new FieldSchemaToAttributeTypeMapper();
11+
public async Task<RuleResult> Validate(FieldSchema fieldSchema, AttributeMetadata attributeMetadata)
12+
{
13+
var schemafieldtype = AttributeTypeMapper.Map(fieldSchema);
14+
if (schemafieldtype == null)
15+
{
16+
return RuleResult.Failure($"Schema Field type {fieldSchema.Type} is not currently supported.");
17+
18+
}
19+
20+
if (schemafieldtype.Value != attributeMetadata.AttributeType)
21+
{
22+
return RuleResult.Failure($"Attribute {fieldSchema.Name} is type of {schemafieldtype} but it's expected to be {attributeMetadata.AttributeType}");
23+
}
24+
return RuleResult.Success();
25+
}
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
2+
using Microsoft.Xrm.Sdk.Metadata;
3+
4+
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules.EntitySchemas.FieldSchemas
5+
{
6+
public interface IFieldSchemaValidationRule
7+
{
8+
Task<RuleResult> Validate(FieldSchema fieldSchema, AttributeMetadata attributeMetadata);
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Common;
2+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
3+
using Microsoft.Xrm.Sdk.Metadata;
4+
5+
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules.EntitySchemas.FieldSchemas
6+
{
7+
public class LookupFieldsTargetsMustMatchValidationRule : IFieldSchemaValidationRule
8+
{
9+
public async Task<RuleResult> Validate(FieldSchema fieldSchema, AttributeMetadata attributeMetadata)
10+
{
11+
if (attributeMetadata is LookupAttributeMetadata lookupMD &&
12+
lookupMD.AttributeType != AttributeTypeCode.Owner &&
13+
!lookupMD.Targets.AreEnumerablesEqualIgnoreOrder(fieldSchema.LookupType?.Split('|') ?? Array.Empty<string>()))
14+
{
15+
return RuleResult.Failure($"LookupAttribute {fieldSchema.Name} targets {fieldSchema.LookupType} but it's expected to target: {string.Join("|", lookupMD.Targets)}");
16+
17+
}
18+
return RuleResult.Success();
19+
}
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
2+
using Microsoft.Xrm.Sdk.Metadata;
3+
4+
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules.EntitySchemas.RelationshipSchemas
5+
{
6+
public interface IRelationshipSchemaValidationRule
7+
{
8+
Task<RuleResult> Validate(string SourceEntityName, RelationshipSchema relationshipSchema, ManyToManyRelationshipMetadata relationshipMetadata);
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
2+
using Microsoft.Xrm.Sdk.Metadata;
3+
4+
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules.EntitySchemas.RelationshipSchemas
5+
{
6+
public class SourceEntityNameMustMatchValidationRule : IRelationshipSchemaValidationRule
7+
{
8+
9+
10+
public async Task<RuleResult> Validate(string SourceEntityName, RelationshipSchema relationshipSchema, ManyToManyRelationshipMetadata relationshipMetadata)
11+
{
12+
if (relationshipMetadata.Entity1LogicalName != SourceEntityName)
13+
{
14+
return RuleResult.Failure($"ManyToMany Relationship Table {relationshipSchema.Name} Source Entity is {SourceEntityName} but it's expected to be {relationshipMetadata.Entity1LogicalName}");
15+
}
16+
17+
return RuleResult.Success();
18+
}
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Dataverse.ConfigurationMigrationTool.Console.Features.Import.Model;
2+
using Microsoft.Xrm.Sdk.Metadata;
3+
4+
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules.EntitySchemas.RelationshipSchemas
5+
{
6+
public class TargetEntityNameMustMatchValidationRule : IRelationshipSchemaValidationRule
7+
{
8+
public async Task<RuleResult> Validate(string SourceEntityName, RelationshipSchema relationshipSchema, ManyToManyRelationshipMetadata relationshipMetadata)
9+
{
10+
if (relationshipMetadata.Entity2LogicalName != relationshipSchema.M2mTargetEntity)
11+
{
12+
return RuleResult.Failure($"ManyToMany Relationship Table {relationshipSchema.Name} Targets Entity is {relationshipSchema.M2mTargetEntity} but it's expected to be {relationshipMetadata.Entity2LogicalName}");
13+
14+
}
15+
return RuleResult.Success();
16+
}
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Dataverse.ConfigurationMigrationTool.Console.Features.Import.Validators.Rules
2+
{
3+
public class RuleResult
4+
{
5+
private RuleResult() { }
6+
private RuleResult(string errorMessage)
7+
{
8+
ErrorMessage = errorMessage;
9+
}
10+
public bool IsSuccess => string.IsNullOrWhiteSpace(ErrorMessage);
11+
public string ErrorMessage { get; }
12+
public static RuleResult Success() => new();
13+
14+
public static RuleResult Failure(string errorMessage) => new(errorMessage);
15+
16+
}
17+
}

0 commit comments

Comments
 (0)