diff --git a/src/Json.Schema.Validation.UnitTests/ValidatorTests.cs b/src/Json.Schema.Validation.UnitTests/ValidatorTests.cs
index 8be5b01..a6c9e31 100644
--- a/src/Json.Schema.Validation.UnitTests/ValidatorTests.cs
+++ b/src/Json.Schema.Validation.UnitTests/ValidatorTests.cs
@@ -1064,9 +1064,101 @@ public void Serialize(IXunitSerializationInfo info)
}
}",
MakeErrorMessage(2, 8, "a", ErrorNumber.DependentPropertyMissing, "x", "\"y\"", "\"y\"")
+ ),
+ new TestCase(
+ "String: format: valid with date-time format constraints",
+ @"{
+ ""type"": ""string"",
+ ""format"": ""date-time""
+ }",
+ "\"2023-02-03T12:34:56.789Z\""
+ ),
+ new TestCase(
+ "String: format: invalid date-time format string",
+ @"{
+ ""type"": ""string"",
+ ""format"": ""date-time""
+ }",
+ "\"failure-string\"",
+ MakeErrorMessage(1, 16, string.Empty, ErrorNumber.StringDoesNotMatchFormat, FormatAttributes.DateTime, "failure-string")
+ ),
+ new TestCase(
+ "String: format: valid with uri format constraint",
+ @"{
+ ""type"": ""string"",
+ ""format"": ""uri""
+ }",
+ "\"https://www.example.com\""
+ ),
+ new TestCase(
+ "String: format: valid with file uri format constraint",
+ @"{
+ ""type"": ""string"",
+ ""format"": ""uri""
+ }",
+ "\"file://localhost/absolute/path/to/file\""
+ ),
+ new TestCase(
+ "String: format: valid with email uri format constraint",
+ @"{
+ ""type"": ""string"",
+ ""format"": ""uri""
+ }",
+ "\"mailto:jsmith@example.com?subject=A%20Test&body=My%20idea%20is%3A%20%0A\""
+ ),
+ new TestCase(
+ "String: format: Invalid uri value",
+ @"{
+ ""type"": ""string"",
+ ""format"": ""uri""
+ }",
+ "\"sites/files/images/picture.png\"",
+ MakeErrorMessage(1, 32, string.Empty, ErrorNumber.StringDoesNotMatchFormat, FormatAttributes.Uri, "sites/files/images/picture.png")
+ ),
+ new TestCase(
+ "String: format: valid with uri-reference format constraint",
+ @"{
+ ""type"": ""string"",
+ ""format"": ""uri-reference""
+ }",
+ "\"sites/files/images/picture.png\""
+ ),
+ new TestCase(
+ "String: format: Invalid with uri-reference format constraint",
+ @"{
+ ""type"": ""string"",
+ ""format"": ""uri-reference""
+ }",
+ "\"````\"",
+ MakeErrorMessage(1, 6, string.Empty, ErrorNumber.StringDoesNotMatchFormat, FormatAttributes.UriReference, "````")
+ ),
+ new TestCase(
+ "String: format: valid with uuid format constraint - lowercase",
+ @"{
+ ""type"": ""string"",
+ ""format"": ""uuid""
+ }",
+ "\"8b524014-4666-449e-afc4-1b82fbfb27ad\""
+ ),
+ new TestCase(
+ "String: format: valid with uuid format constraint - uppercase",
+ @"{
+ ""type"": ""string"",
+ ""format"": ""uuid""
+ }",
+ "\"6303D187-07A8-4164-864F-0C01D6F8E5C4\""
+ ),
+ new TestCase(
+ "String: format: Invalid with uuid format constraint",
+ @"{
+ ""type"": ""string"",
+ ""format"": ""uuid""
+ }",
+ "\"4014-4666-449e-afc4-1b82fbfb27ad\"",
+ MakeErrorMessage(1, 34, string.Empty, ErrorNumber.StringDoesNotMatchFormat, FormatAttributes.Uuid, "4014-4666-449e-afc4-1b82fbfb27ad")
)
};
-
+
[Theory(DisplayName = "Validation")]
[MemberData(nameof(TestCases))]
public void Tests(TestCase test)
diff --git a/src/Json.Schema.Validation/RuleFactory.cs b/src/Json.Schema.Validation/RuleFactory.cs
index 39c67d9..7861cc0 100644
--- a/src/Json.Schema.Validation/RuleFactory.cs
+++ b/src/Json.Schema.Validation/RuleFactory.cs
@@ -198,7 +198,12 @@ private static ReportingDescriptor MakeRule(ErrorNumber errorNumber, string full
[ErrorNumber.DependentPropertyMissing] = MakeRule(
ErrorNumber.DependentPropertyMissing,
RuleResources.RuleDescriptionDependentPropertyMissing,
- RuleResources.ErrorDependentPropertyMissing)
+ RuleResources.ErrorDependentPropertyMissing),
+
+ [ErrorNumber.StringDoesNotMatchFormat] = MakeRule(
+ ErrorNumber.StringDoesNotMatchFormat,
+ RuleResources.RuleDescriptionStringDoesNotMatchFormat,
+ RuleResources.ErrorStringDoesNotMatchFormat)
});
public static ReportingDescriptor GetRuleFromRuleId(string ruleId)
diff --git a/src/Json.Schema.Validation/RuleResources.Designer.cs b/src/Json.Schema.Validation/RuleResources.Designer.cs
index b641467..bf32a52 100644
--- a/src/Json.Schema.Validation/RuleResources.Designer.cs
+++ b/src/Json.Schema.Validation/RuleResources.Designer.cs
@@ -357,7 +357,18 @@ internal static string ErrorWrongType {
return ResourceManager.GetString("ErrorWrongType", resourceCulture);
}
}
-
+
+ ///
+ /// Looks up a localized string similar to The schema requires string value of the format "{1}", but a token value "{2}" was found...
+ ///
+ internal static string ErrorStringDoesNotMatchFormat
+ {
+ get
+ {
+ return ResourceManager.GetString("ErrorStringDoesNotMatchFormat", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to An object contains a property not defined by the schema is present, and the schema does not permit additional properties..
///
@@ -645,5 +656,16 @@ internal static string RuleDescriptionWrongType {
return ResourceManager.GetString("RuleDescriptionWrongType", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to An string value has a format that is not permitted by the schema's "format" property..
+ ///
+ internal static string RuleDescriptionStringDoesNotMatchFormat
+ {
+ get
+ {
+ return ResourceManager.GetString("RuleDescriptionStringDoesNotMatchFormat", resourceCulture);
+ }
+ }
}
}
diff --git a/src/Json.Schema.Validation/RuleResources.resx b/src/Json.Schema.Validation/RuleResources.resx
index 366b49e..af7427c 100644
--- a/src/Json.Schema.Validation/RuleResources.resx
+++ b/src/Json.Schema.Validation/RuleResources.resx
@@ -217,6 +217,9 @@
The schema requires one of the types [{1}], but a token of type '{2}' was found.
+
+ The schema requires string value of the format '{1}', but a token value '{2}' was found.
+
An object contains a property not defined by the schema, and the schema does not permit additional properties.
@@ -313,4 +316,7 @@
An instance has a type that is not permitted by the schema's 'type' property.
+
+ A string value has a format that is not permitted by the schema's 'format' property.
+
\ No newline at end of file
diff --git a/src/Json.Schema.Validation/Validator.cs b/src/Json.Schema.Validation/Validator.cs
index 88193f6..40bbd46 100644
--- a/src/Json.Schema.Validation/Validator.cs
+++ b/src/Json.Schema.Validation/Validator.cs
@@ -181,6 +181,44 @@ private void ValidateString(JValue jValue, JsonSchema schema)
AddResult(jValue, ErrorNumber.StringDoesNotMatchPattern, value, schema.Pattern);
}
}
+
+ if(!string.IsNullOrEmpty(schema.Format))
+ {
+ ValidateStringFormat(jValue, schema.Format);
+ }
+ }
+
+ private void ValidateStringFormat(JValue jValue, string format)
+ {
+ string value = jValue.Value();
+ if (string.Compare(format, FormatAttributes.DateTime) == 0)
+ {
+ if (!DateTime.TryParse(value, out DateTime _))
+ {
+ AddResult(jValue, ErrorNumber.StringDoesNotMatchFormat, format, value);
+ }
+ }
+ else if(string.Compare(format, FormatAttributes.Uri) == 0)
+ {
+ if (!Uri.IsWellFormedUriString(value, UriKind.Absolute))
+ {
+ AddResult(jValue, ErrorNumber.StringDoesNotMatchFormat, format, value);
+ }
+ }
+ else if(string.Compare(format, FormatAttributes.UriReference) == 0)
+ {
+ if (!Uri.IsWellFormedUriString(value, UriKind.RelativeOrAbsolute))
+ {
+ AddResult(jValue, ErrorNumber.StringDoesNotMatchFormat, format, value);
+ }
+ }
+ else if (string.Compare(format, FormatAttributes.Uuid) == 0)
+ {
+ if (!Guid.TryParse(value, out Guid _))
+ {
+ AddResult(jValue, ErrorNumber.StringDoesNotMatchFormat, format, value);
+ }
+ }
}
// Compute the length of a string, counting surrogate pairs as one
diff --git a/src/Json.Schema/ErrorNumber.cs b/src/Json.Schema/ErrorNumber.cs
index 4e8c8e8..c5cc172 100644
--- a/src/Json.Schema/ErrorNumber.cs
+++ b/src/Json.Schema/ErrorNumber.cs
@@ -474,7 +474,27 @@ public enum ErrorNumber
/// The instance contains a property specified by "dependencies", but it does
/// not contain all the properties specified by the corresponding property dependency.
///
- DependentPropertyMissing = 1023
+ DependentPropertyMissing = 1023,
+
+
+ ///
+ /// A string instance does not match the required format.
+ ///
+ ///
+ /// Schema:
+ ///
+ /// {
+ /// "type": "string",
+ /// "format": "date-time"
+ /// }
+ ///
+ ///
+ /// Instance:
+ ///
+ /// "2023-02-03:T12:00:00Z"
+ ///
+ ///
+ StringDoesNotMatchFormat = 1024,
#endregion Errors in instance document
}