in src/Elastic.Apm.Specification/Validator.cs [370:514]
private static void ValidateProperties(Type specType, JsonSchema schema, ImplementationProperty[] properties, TypeValidationResult result)
{
IReadOnlyDictionary<string, JsonSchemaProperty> schemaProperties;
try
{
schemaProperties = schema.ActualProperties;
}
catch (InvalidOperationException e)
{
throw new JsonSchemaException($"Cannot get schema properties for {schema.GetNameOrSpecificationId()}. {e.Message}", e);
}
foreach (var kv in schemaProperties)
{
var schemaProperty = kv.Value.ActualSchema;
var name = kv.Value.Name;
var specTypeProperty = properties.SingleOrDefault(p => p.Name == name);
if (specTypeProperty == null)
{
// No "type" means any type, which maps to None
if (schemaProperty.Type.HasFlag(JsonObjectType.None) || schemaProperty.Type.HasFlag(JsonObjectType.Null))
{
if (result.Validation == Validation.SpecToType)
result.AddError(TypeValidationError.NotFound(specType, schema.GetNameOrSpecificationId(), name));
}
else
result.AddError(TypeValidationError.NotFound(specType, schema.GetNameOrSpecificationId(), name));
continue;
}
// check certain .NET types first before defaulting to the JSON schema flags.
// A property might be represented as more than one type e.g. ["integer", "string", "null],
// so it's better to look at the .NET type first and try to look for the associated JSON schema flag.
switch (specTypeProperty.PropertyType.FullName)
{
case "System.UInt16":
case "System.Int16":
case "System.Byte":
case "System.SByte":
case "System.UInt32":
case "System.Single":
case "System.Decimal":
case "System.Double":
case "System.UInt64":
if (schemaProperty.Type.HasFlag(JsonObjectType.Number))
ValidateNumber(specType, schema, schemaProperty, specTypeProperty, result);
else
{
result.AddError(
TypeValidationError.ExpectedType(
specType,
specTypeProperty.PropertyType,
"number",
schema.GetNameOrSpecificationId(),
name));
}
break;
case "System.Int64":
case "System.Int32":
if (schemaProperty.Type.HasFlag(JsonObjectType.Number))
ValidateNumber(specType, schema, schemaProperty, specTypeProperty, result);
else if (schemaProperty.Type.HasFlag(JsonObjectType.Integer))
ValidateInteger(specType, schema, schemaProperty, specTypeProperty, result);
else
{
result.AddError(
TypeValidationError.ExpectedType(
specType,
specTypeProperty.PropertyType,
"integer",
schema.GetNameOrSpecificationId(),
name));
}
break;
case "System.String":
if (schemaProperty.Type.HasFlag(JsonObjectType.String))
ValidateString(specType, schema, schemaProperty, specTypeProperty, result);
else
{
result.AddError(
TypeValidationError.ExpectedType(
specType,
specTypeProperty.PropertyType,
"string",
schema.GetNameOrSpecificationId(),
name));
}
break;
case "System.Boolean":
if (schemaProperty.Type.HasFlag(JsonObjectType.Boolean))
ValidateBoolean(specType, schema, schemaProperty, specTypeProperty, result);
else
{
result.AddError(
TypeValidationError.ExpectedType(
specType,
specTypeProperty.PropertyType,
"boolean",
schema.GetNameOrSpecificationId(),
name));
}
break;
default:
// Are there multiple types? If so, based on the .NET type not being a primitive type, we would expect the presence of the
// schema "object" or "array" type in the majority of cases, so check these first.
// For types with custom serialization, the schema type may be a primitive type, so we can't easily statically validate it.
if (HasMultipleNonNullTypes(schemaProperty.Type))
{
if (schemaProperty.Type.HasFlag(JsonObjectType.Object))
ValidateProperties(specTypeProperty.PropertyType, schemaProperty, result);
else if (schemaProperty.Type.HasFlag(JsonObjectType.Array) &&
IsEnumerableType(specTypeProperty.PropertyType, out var elementType))
ValidateProperties(elementType, schemaProperty.Item.ActualSchema, result);
else
{
result.AddIgnore(new TypeValidationIgnore(schema.GetNameOrSpecificationId(), name,
$"Cannot statically check type. .NET type '{specType}', schema type '{schemaProperty.Type}'"));
}
}
else if (schemaProperty.Type.HasFlag(JsonObjectType.Boolean))
ValidateBoolean(specType, schema, schemaProperty, specTypeProperty, result);
else if (schemaProperty.Type.HasFlag(JsonObjectType.Integer))
ValidateInteger(specType, schema, schemaProperty, specTypeProperty, result);
else if (schemaProperty.Type.HasFlag(JsonObjectType.Number))
ValidateNumber(specType, schema, schemaProperty, specTypeProperty, result);
else if (schemaProperty.Type.HasFlag(JsonObjectType.String))
{
if (schemaProperty.IsEnumeration && specTypeProperty.PropertyType.IsEnum)
ValidateEnum(specType, schema, schemaProperty, specTypeProperty, result);
else
ValidateString(specType, schema, schemaProperty, specTypeProperty, result);
}
else if (schemaProperty.Type.HasFlag(JsonObjectType.Object))
ValidateProperties(specTypeProperty.PropertyType, schemaProperty, result);
else if (schemaProperty.Type.HasFlag(JsonObjectType.Array))
{
if (IsEnumerableType(specTypeProperty.PropertyType, out var elementType))
ValidateProperties(elementType, schemaProperty.Item.ActualSchema, result);
}
break;
}
}
}