Last active
February 23, 2022 18:05
-
-
Save leandromoh/f85181d843cc6b3661d00cc84490d192 to your computer and use it in GitHub Desktop.
Validate dynamic schema on C#
This file contains hidden or 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
public class CreateRequest | |
{ | |
public string Name { get; set; } | |
public string CreatedBy { get; set; } | |
public JObject Schema { get; set; } | |
} | |
public class CreateRequestValidator : AbstractValidator<CreateRequest> | |
{ | |
private readonly Regex _indexer = new Regex(@"\[\d+\]", RegexOptions.Compiled); | |
private readonly IReadOnlyDictionary<string, Type> _validSchemas; | |
public CreateOrderTemplateRequestValidator() | |
{ | |
RuleFor(x => x.Name).NotEmpty(); | |
RuleFor(x => x.CreatedBy).NotEmpty(); | |
var requestTypes = GetAllOrderRequestTypes(typeof(SendRequest)); | |
var schemas = requestTypes.SelectMany(type => GetSchema(type)); | |
var validSchemas = new Dictionary<string, Type>(); | |
foreach (var (key, value) in schemas) | |
{ | |
validSchemas[key] = value; | |
} | |
_validSchemas = validSchemas; | |
RuleFor(x => x.Schema).Custom((schema, context) => | |
{ | |
var flattened = schema | |
.Descendants() | |
.OfType<JValue>() | |
.ToDictionary(jv => jv.Path, jv => jv); | |
foreach (var (path, value) in flattened) | |
{ | |
var adjustedPath = _indexer.Replace(path, "[]"); | |
if (_validSchemas.TryGetValue(adjustedPath, out var expectedType)) | |
{ | |
try | |
{ | |
value.ToObject(expectedType); | |
} | |
catch | |
{ | |
context.AddFailure($"'{adjustedPath}' has type '{expectedType.Name}', value '{value}' is not valid."); | |
} | |
} | |
else | |
{ | |
context.AddFailure($"'{adjustedPath}' is not a valid."); | |
} | |
} | |
}); | |
} | |
private IReadOnlyDictionary<string, Type> GetSchema(Type type) | |
{ | |
var fixture = new Fixture() { RepeatCount = 1 }; | |
var instance = fixture.Create(type, new SpecimenContext(fixture)); | |
var json = JsonConvert.SerializeObject(instance); | |
var schema = JObject.Parse(json); | |
var flattened = schema | |
.Descendants() | |
.OfType<JValue>() | |
.ToDictionary(jv => _indexer.Replace(jv.Path, "[]"), jv => | |
{ | |
var parts = jv.Path.Split("."); | |
Type type = instance.GetType(); | |
foreach (var member in parts) | |
{ | |
var isCollection = member.EndsWith("]"); | |
if (isCollection) | |
{ | |
var collectionMember = _indexer.Replace(member, string.Empty); | |
type = type.GetProperty(collectionMember).PropertyType.GenericTypeArguments.First(); | |
} | |
else | |
{ | |
type = type.GetProperty(member).PropertyType; | |
} | |
} | |
return type; | |
}); | |
return flattened; | |
} | |
private static IEnumerable<Type> GetAllOrderRequestTypes(Type baseOrderRequest) | |
{ | |
return baseOrderRequest.Assembly.GetTypes().Where(type => type.IsSubclassOf(baseOrderRequest)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment