private static void ValidateString(Type specType, JsonSchema schema, JsonSchema property, ImplementationProperty specTypeProperty, TypeValidationResult result ) { CheckType(specType, "string", schema, property, specTypeProperty, result, t => t == typeof(string)); CheckMaxLength(specType, schema, property, specTypeProperty, result); }
private static void CheckType(Type specType, string expectedType, JsonSchema schema, JsonSchema property, ImplementationProperty implementationProperty, TypeValidationResult result, Func <Type, bool> typeCheck ) { var propertyType = implementationProperty.PropertyType; var nullable = !propertyType.IsValueType; if (IsNullableType(propertyType)) { nullable = true; propertyType = Nullable.GetUnderlyingType(implementationProperty.PropertyType); } if (!typeCheck(propertyType)) { result.AddError(TypeValidationError.ExpectedType(specType, propertyType, expectedType, schema.GetNameOrSpecificationId(), property.GetNameOrSpecificationId())); } if (result.Validation == Validation.SpecToType && property.Type.HasFlag(JsonObjectType.Null) && !nullable) { result.AddError(new TypeValidationError(specType, schema.GetNameOrSpecificationId(), property.GetNameOrSpecificationId(), "expected type to be nullable")); } }
private async Task <TypeValidationResult> ValidateAsync(Type type, string specificationId, Validation validation) { var schema = await LoadSchemaAsync(specificationId).ConfigureAwait(false); var result = new TypeValidationResult(type, specificationId, validation); ValidateProperties(type, schema, result); foreach (var inheritedSchema in schema.AllInheritedSchemas) { ValidateProperties(type, inheritedSchema, result); } return(result); }
private static void CheckMaxLength(Type specType, JsonSchema schema, JsonSchema schemaProperty, ImplementationProperty implementationProperty, TypeValidationResult result ) { if (schemaProperty.MaxLength.HasValue) { var maxLength = schemaProperty.MaxLength.Value; if (!implementationProperty.MaxLength.HasValue) { result.AddError(new TypeValidationError(specType, schema.GetNameOrSpecificationId(), implementationProperty.Name, $"expected property to enforce maxLength of {maxLength} but does not")); } else if (implementationProperty.MaxLength != maxLength) { result.AddError(new TypeValidationError(specType, schema.GetNameOrSpecificationId(), implementationProperty.Name, $"property max length {implementationProperty.MaxLength} not equal to spec maxLength {maxLength}")); } } }
private static void ValidateNumber(Type specType, JsonSchema schema, JsonSchema property, ImplementationProperty specTypeProperty, TypeValidationResult result ) => CheckType(specType, "number", schema, property, specTypeProperty, result, t => NumericTypeNames.Contains(t.FullName));
private static void ValidateInteger(Type specType, JsonSchema schema, JsonSchema property, ImplementationProperty specTypeProperty, TypeValidationResult result ) => CheckType(specType, "integer", schema, property, specTypeProperty, result, t => t == typeof(int) || t == typeof(long));
private static void ValidateBoolean(Type specType, JsonSchema schema, JsonSchema property, ImplementationProperty specTypeProperty, TypeValidationResult result ) => CheckType(specType, "boolean", schema, property, specTypeProperty, result, t => t == typeof(bool));
private static void ValidateEnum(Type specType, JsonSchema schema, JsonSchema schemaProperty, ImplementationProperty specTypeProperty, TypeValidationResult result ) { var enumValues = GetEnumValues(specTypeProperty.PropertyType); foreach (var enumValue in enumValues) { if (!schemaProperty.Enumeration.Cast <string>().Contains(enumValue)) { result.AddError(new TypeValidationError(specType, schema.GetNameOrSpecificationId(), schemaProperty.GetNameOrSpecificationId(), $"enum did not contain value {enumValue}")); } } }
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; } } }
private static void ValidateProperties(Type specType, JsonSchema schema, TypeValidationResult result) { ImplementationProperty[] properties; try { properties = GetProperties(specType); } catch (ContractResolveException e) { result.AddIgnore(new TypeValidationIgnore(schema.GetNameOrSpecificationId(), specType.Name, e.Message)); return; } ValidateProperties(specType, schema, properties, result); foreach (var inheritedSchema in schema.AllInheritedSchemas) { ValidateProperties(specType, inheritedSchema, properties, result); } if (schema.AnyOf.Count > 0) { var anyOfResults = Enumerable .Range(1, schema.AnyOf.Count) .Select(_ => new TypeValidationResult(specType, result.SpecificationId, result.Validation)) .ToList(); var index = 0; foreach (var anyOfSchema in schema.AnyOf) { ValidateProperties(specType, anyOfSchema, properties, anyOfResults[index]); ++index; } // at least one must be successful if (!anyOfResults.Any(r => r.Success)) { var errors = anyOfResults.Select(r => r.ToString()); result.AddError(new TypeValidationError(specType, schema.GetNameOrSpecificationId(), specType.Name, $"anyOf failure: {string.Join(",", errors)}")); } } if (schema.OneOf.Count > 0) { var oneOfResults = Enumerable .Range(1, schema.OneOf.Count) .Select(_ => new TypeValidationResult(specType, result.SpecificationId, result.Validation)) .ToList(); var index = 0; foreach (var oneOfSchema in schema.OneOf) { ValidateProperties(specType, oneOfSchema, properties, oneOfResults[index]); ++index; } // only one must be successful if (!oneOfResults.Any(r => r.Success)) { var errors = oneOfResults.Select(r => r.ToString()); result.AddError(new TypeValidationError(specType, schema.GetNameOrSpecificationId(), specType.Name, $"oneOf all failure: {string.Join(",", errors)}")); } else if (oneOfResults.Count(r => r.Success) > 0) { var errors = oneOfResults.Where(r => r.Success).Select(r => r.ToString()); result.AddError(new TypeValidationError(specType, schema.GetNameOrSpecificationId(), specType.Name, $"oneOf more than one successful failure: {string.Join(",", errors)}")); } } }