public void ResourceStringValidationBadTimestamp() { var testFile = GetDocFile(); var resource = testFile.Resources.Single(x => x.Name == "example.resource"); var schema = new JsonSchema(resource); var method = testFile.Requests.Single(x => x.Identifier == "bad-timestamp"); ValidationError[] detectedErrors; bool result = schema.ValidateExpectedResponse(method, out detectedErrors); Assert.IsFalse(result); Assert.IsTrue(detectedErrors.WereErrors()); Assert.IsNotNull(detectedErrors.SingleOrDefault(x => x.Code == ValidationErrorCode.ExpectedTypeDifferent)); }
/// <summary> /// Examines input json string to ensure that it compiles with the JsonSchema definition. Any errors in the /// validation of the schema are returned via the errors out parameter. /// </summary> /// <param name="schema">Schemas definition used as a reference.</param> /// <param name="inputJson">Input json example to be validated</param> /// <param name="errors">Out parameter that provides any errors, warnings, or messages that were generated</param> /// <param name="expectedJson"></param> /// <returns></returns> public bool ValidateJsonCompilesWithSchema(JsonSchema schema, JsonExample inputJson, out ValidationError[] errors, JsonExample expectedJson = null, ValidationOptions options = null) { if (null == schema) throw new ArgumentNullException("schema"); if (null == inputJson) throw new ArgumentNullException("inputJson"); string collectionPropertyName = "value"; if (null != inputJson.Annotation && null != inputJson.Annotation.CollectionPropertyName) { collectionPropertyName = inputJson.Annotation.CollectionPropertyName; } // If we didn't get an options, create a new one with some defaults provided by the annotation options = options ?? new ValidationOptions(); options.AllowTruncatedResponses = (inputJson.Annotation ?? new CodeBlockAnnotation()).TruncatedResult; options.CollectionPropertyName = collectionPropertyName; return schema.ValidateJson(inputJson, out errors, this.registeredSchema, options, expectedJson); }
/// <summary> /// Validates the value of json according to an implicit schmea defined by expectedJson /// </summary> /// <param name="expectedResponseAnnotation"></param> /// <param name="actualResponseBodyJson"></param> /// <param name="errors"></param> /// <returns></returns> public bool ValidateJsonExample(CodeBlockAnnotation expectedResponseAnnotation, string actualResponseBodyJson, out ValidationError[] errors, ValidationOptions options = null) { List<ValidationError> newErrors = new List<ValidationError>(); var resourceType = expectedResponseAnnotation.ResourceType; if (resourceType == "stream") { // No validation since we're streaming data errors = null; return true; } else { JsonSchema schema; if (string.IsNullOrEmpty(resourceType)) { schema = JsonSchema.EmptyResponseSchema; } else if (!this.registeredSchema.TryGetValue(resourceType, out schema)) { newErrors.Add(new ValidationWarning(ValidationErrorCode.ResponseResourceTypeMissing, null, "Missing required resource: {0}. Validation limited to basics only.", resourceType)); // Create a new schema based on what's avaiable in the json schema = new JsonSchema(actualResponseBodyJson, new CodeBlockAnnotation { ResourceType = expectedResponseAnnotation.ResourceType }); } ValidationError[] validationJsonOutput; this.ValidateJsonCompilesWithSchema(schema, new JsonExample(actualResponseBodyJson, expectedResponseAnnotation), out validationJsonOutput, options: options); newErrors.AddRange(validationJsonOutput); errors = newErrors.ToArray(); return errors.Length == 0; } }
public void RegisterJsonResource(ResourceDefinition resource) { var schema = new JsonSchema(resource); this.registeredSchema[resource.Metadata.ResourceType] = schema; }
/// <summary> /// Returns a JSON schema reference, either by finding an example in the registered schema or by /// creating a new temporary schema from the fallback. /// </summary> /// <param name="resourceType"></param> /// <param name="errors"></param> /// <param name="jsonStringForFallbackIfMissingResource"></param> /// <returns></returns> protected JsonSchema GetJsonSchema(string resourceType, IList<ValidationError> errors, string jsonStringForFallbackIfMissingResource) { JsonSchema schema; if (string.IsNullOrEmpty(resourceType)) { errors.Add(new ValidationMessage(null, "Resource type was null or missing, so we assume there is no response to validate.")); schema = JsonSchema.EmptyResponseSchema; } else if (!this.registeredSchema.TryGetValue(resourceType, out schema) && !string.IsNullOrEmpty(jsonStringForFallbackIfMissingResource)) { errors.Add(new ValidationWarning(ValidationErrorCode.ResponseResourceTypeMissing, null, "Missing required resource: {0}. Validation based on fallback example.", resourceType)); // Create a new schema based on what's avaiable in the expected response JSON schema = new JsonSchema(jsonStringForFallbackIfMissingResource, new CodeBlockAnnotation { ResourceType = resourceType }); } return schema; }
private static JsonProperty ParseProperty(JToken token, JsonSchema containerSchema, List<ValidationError> detectedErrors = null) { JsonProperty propertyInfo = null; if (token.Type == JTokenType.Property) { JProperty tokenProperty = (JProperty)token; propertyInfo = ParseProperty(tokenProperty.Name, tokenProperty.Value, containerSchema); } else { if (detectedErrors != null) { detectedErrors.Add( new ValidationWarning( ValidationErrorCode.JsonParserException, token.Path, "Unhandled token type: " + token.Type)); } else { Console.WriteLine("Unhandled token type: " + token.Type); } } return propertyInfo; }
private static JsonProperty ParseProperty(string name, JToken value, JsonSchema containerSchema) { switch (value.Type) { case JTokenType.Boolean: return new JsonProperty { Name = name, Type = JsonDataType.Boolean, OriginalValue = value.ToString() }; case JTokenType.Float: case JTokenType.Integer: return new JsonProperty { Name = name, Type = JsonDataType.Number, OriginalValue = value.ToString() }; case JTokenType.String: return new JsonProperty { Name = name, Type = JsonDataType.String, OriginalValue = value.ToString() }; case JTokenType.Date: return new JsonProperty { Name = name, Type = JsonDataType.String, OriginalValue = value.ToString() }; case JTokenType.Object: { var objectSchema = ObjectToSchema((JObject)value); if (objectSchema.ContainsKey("@odata.type")) { return new JsonProperty { Name = name, Type = JsonDataType.ODataType, ODataTypeName = objectSchema["@odata.type"].OriginalValue }; } else { // See if we can infer type from the parent scehma JsonProperty schemaProperty; JsonDataType propertyType = JsonDataType.Object; string odataTypeName = null; if (null != containerSchema && containerSchema.ExpectedProperties.TryGetValue(name, out schemaProperty)) { odataTypeName = schemaProperty.ODataTypeName; propertyType = schemaProperty.Type; } return new JsonProperty { Name = name, Type = propertyType, ODataTypeName = odataTypeName, CustomMembers = ObjectToSchema((JObject)value) }; } } case JTokenType.Array: { // Array JsonDataType propertyType = JsonDataType.Array; string odataTypeName = null; // Infer type from the items in the array var firstChild = value.First; if (null != firstChild) { var objectType = ParseProperty("array[0]", firstChild, null); if (null != objectType) { odataTypeName = objectType.ODataTypeName; propertyType = objectType.Type; } } else { propertyType = JsonDataType.Array; } // See if we can do better than just Custom if (propertyType == JsonDataType.Object) { JsonProperty schemaProperty; if (null != containerSchema && containerSchema.ExpectedProperties.TryGetValue(name, out schemaProperty)) { // Use the parent schema's type indication odataTypeName = schemaProperty.ODataTypeName; propertyType = schemaProperty.Type; } } Dictionary<string, JsonProperty> members = null; if (propertyType == JsonDataType.Object || propertyType == JsonDataType.Array) { var firstValue = (JObject)value.First; members = firstValue != null ? ObjectToSchema(firstValue) : new Dictionary<string, JsonProperty>(); } return new JsonProperty { Name = name, Type = propertyType, ODataTypeName = odataTypeName, IsArray = true, OriginalValue = value.ToString(), CustomMembers = members }; } case JTokenType.Null: return new JsonProperty { Name = name, Type = JsonDataType.Object, IsArray = false, OriginalValue = null }; default: Console.WriteLine("Unsupported: Property {0} is of type {1} which is not currently supported.", name, value.Type); throw new NotSupportedException(string.Format("Unsupported: Property {0} is of type {1} which is not currently supported.", name, value.Type)); } }
/// <summary> /// Validate the input json against the defined scehma when the instance was created. /// </summary> /// <param name="jsonInput">Input json to validate against schema</param> /// <param name="errors">Array of errors if the validation fails</param> /// <param name="otherSchemas"></param> /// <param name="options"></param> /// <param name="expectedJson"></param> /// <returns>True if validation was successful, otherwise false.</returns> public bool ValidateJson(JsonExample jsonInput, out ValidationError[] errors, Dictionary<string, JsonSchema> otherSchemas, ValidationOptions options, JsonExample expectedJson = null) { JContainer obj; try { var settings = new JsonSerializerSettings { DateParseHandling = DateParseHandling.None, NullValueHandling = NullValueHandling.Include, DefaultValueHandling = DefaultValueHandling.Include }; obj = (JContainer)JsonConvert.DeserializeObject(jsonInput.JsonData, settings); } catch (Exception ex) { errors = new ValidationError[] { new ValidationError(ValidationErrorCode.JsonParserException, null, "Failed to parse json string: {0}. Json: {1}", ex.Message, jsonInput.JsonData) }; return false; } var annotation = jsonInput.Annotation ?? new CodeBlockAnnotation(); List<ValidationError> detectedErrors = new List<ValidationError>(); bool expectErrorObject = (jsonInput.Annotation != null) && jsonInput.Annotation.ExpectError; // Check for an error response dynamic errorObject = obj["error"]; if (null != errorObject && !expectErrorObject) { string code = errorObject.code; string message = errorObject.message; detectedErrors.Clear(); detectedErrors.Add(new ValidationError(ValidationErrorCode.JsonErrorObject, null, "Error response received. Code: {0}, Message: {1}", code, message)); errors = detectedErrors.ToArray(); return false; } else if (expectErrorObject && null == errorObject) { detectedErrors.Clear(); detectedErrors.Add(new ValidationError(ValidationErrorCode.JsonErrorObjectExpected, null, "Expected an error object response, but didn't receive one.")); errors = detectedErrors.ToArray(); return false; } // Check to see if this is a "collection" instance if (null != annotation && annotation.IsCollection) { this.ValidateCollectionObject(obj, annotation, otherSchemas, options.CollectionPropertyName, detectedErrors); } // otherwise verify the object matches this schema else { options = options ?? new ValidationOptions(annotation); if (null != expectedJson) { var expectedJsonSchema = new JsonSchema(expectedJson.JsonData, expectedJson.Annotation); options.ExpectedJsonSchema = expectedJsonSchema; options.RequiredPropertyNames = expectedJsonSchema.ExpectedProperties.Keys.ToArray(); } this.ValidateContainerObject(obj, options, otherSchemas, detectedErrors); } errors = detectedErrors.ToArray(); return detectedErrors.Count == 0; }
/// <summary> /// Build a ParameterDefinition instance based on a property name and provide value and schema. /// </summary> /// <param name="name"></param> /// <param name="value"></param> /// <param name="containerSchema"></param> /// <returns></returns> internal static ParameterDefinition ParseProperty(string name, JToken value, JsonSchema containerSchema) { ParameterDefinition param = new ParameterDefinition(); param.Name = name; param.OriginalValue = value.ToString(); switch (value.Type) { case JTokenType.Boolean: param.Type = ParameterDataType.Boolean; break; case JTokenType.Float: param.Type = ParameterDataType.Float; break; case JTokenType.Integer: param.Type = ParameterDataType.Int64; break; case JTokenType.String: var propValue = value.Value<string>(); SimpleDataType customType = ExtensionMethods.ParseSimpleTypeString(propValue.ToLowerInvariant()); ParameterDataType paramType = (customType != SimpleDataType.None) ? new ParameterDataType(customType) : ParameterDataType.String; param.Type = paramType; break; case JTokenType.Date: param.Type = ParameterDataType.DateTimeOffset; break; case JTokenType.Object: { var objectSchema = GeneratePropertyCollection((JObject)value); if (objectSchema.ContainsKey("@odata.type")) { param.Type = new ParameterDataType(objectSchema["@odata.type"].OriginalValue); } else if (objectSchema.ContainsKey("@type")) { param.Type = new ParameterDataType(objectSchema["@type"].OriginalValue); } else { // See if we can infer type from the parent scehma var propertyCollection = GeneratePropertyCollection((JObject)value); ParameterDefinition[] customMembers = null; if (propertyCollection != null) { customMembers = propertyCollection.Values.ToArray(); } ParameterDefinition schemaProperty; if (null != containerSchema && containerSchema.ExpectedProperties.TryGetValue(name, out schemaProperty)) { param.Type = new ParameterDataType(schemaProperty.Type, customMembers); } else { param.Type = new ParameterDataType(customMembers); } } break; } case JTokenType.Array: { ParameterDataType propertyType = ParameterDataType.GenericCollection; // Try to infer type from the items in the array var firstChild = value.First; if (null != firstChild) { var objectType = ParseProperty("array[0]", firstChild, null); if (null != objectType) { propertyType = ParameterDataType.CollectionOfType(objectType.Type); } } // See if we can do better than GenericCollection if that's the situation we're in. if (propertyType == ParameterDataType.GenericCollection) { ParameterDefinition schemaProperty; if (null != containerSchema && containerSchema.ExpectedProperties.TryGetValue(name, out schemaProperty)) { // Use the parent schema's type indication //propertyType = ParameterDataType.CollectionOfType(schemaProperty.Type); propertyType = schemaProperty.Type; } } Dictionary<string, ParameterDefinition> members = null; if ((propertyType.IsObject || (propertyType.IsCollection && propertyType.CollectionResourceType == SimpleDataType.Object)) && string.IsNullOrEmpty(propertyType.CustomTypeName)) { // If we don't know what kind of object is here, let's record what we see as custom members var firstValue = (JObject)value.First; members = firstValue != null ? GeneratePropertyCollection(firstValue) : new Dictionary<string, ParameterDefinition>(); } ParameterDefinition[] customMembers = null; if (members != null) customMembers = members.Values.ToArray(); param.Type = new ParameterDataType(propertyType, customMembers); break; } case JTokenType.Null: param.Type = ParameterDataType.GenericObject; param.OriginalValue = null; break; default: Console.WriteLine("Unsupported: Property {0} is of type {1} which is not currently supported.", name, value.Type); throw new NotSupportedException(string.Format("Unsupported: Property {0} is of type {1} which is not currently supported.", name, value.Type)); } return param; }
public void ResourceStringValidationValidExampleTest() { var testFile = GetDocFile(); var resource = testFile.Resources.Single(x => x.Name == "example.resource"); var schema = new JsonSchema(resource); var method = testFile.Requests.Single(x => x.Identifier == "valid-response"); ValidationError[] detectedErrors; bool result = schema.ValidateExpectedResponse(method, out detectedErrors); Assert.IsTrue(result); Assert.IsEmpty(detectedErrors, "Validation errors were detected"); }
public void RegisterJsonResource(ResourceDefinition resource) { var schema = new JsonSchema(resource); this.registeredSchema[resource.Name] = schema; }