/// <summary> /// Validates a required property. /// </summary> /// <param name="value"></param> /// <param name="context"></param> /// <param name="level"></param> /// <param name="description"></param> /// <param name="result"></param> public static void ValidateRequiredProperty(string value, string context, ViolationLevel level, string description, ViolationCollection result) { var valid = value != null; if (!valid) result.Add(new Violation() { Code = context, ViolationLevel = level, Context = context, Description = description }); }
/// <summary> /// Validate a Url that is NOT required. /// </summary> /// <param name="value"></param> /// <param name="context"></param> /// <param name="level"></param> /// <param name="description"></param> /// <param name="result"></param> public static void ValidateRequiredUrl(string value, string context, ViolationLevel level, string description, ViolationCollection result) { try { if (value == null || value == "") { result.Add(new Violation() { Code = context, ViolationLevel = level, Context = context, Description = description }); } else { Uri uri = new Uri(value); } // If we get to here, the uri is valid. } catch { result.Add(new Violation() { Code = context, ViolationLevel = level, Context = context, Description = description }); } }
/// <summary> /// Validates the ExternalDocs property. No external Docs property is actually required; so null is an acceptable value. /// </summary> /// <param name="value"></param> /// <param name="context"></param> /// <param name="level"></param> /// <param name="description"></param> /// <param name="result"></param> public static void ValidateExternalDocs(ExternalDocumentationObject value, string context, string description, string urlDescription, ViolationCollection result) { if (value == null) return; var externalDocsValid = (value != null); if (!externalDocsValid) { result.Add(new Violation() { Code = context, ViolationLevel = ViolationLevel.Informational, Context = context, Description = description }); } else { // swagger.ExternalDocs is not null. if (value.Description == null) result.Add(new Violation() { Code = string.Format("{0}.Description", context), ViolationLevel = ViolationLevel.Informational, Context = string.Format("{0}.Description", context), Description = urlDescription }); ValidateRequiredUrl(value.Url, string.Format("{0}.Url", context), ViolationLevel.Error, urlDescription, result); } }
/// <summary> /// Validate a Url that is NOT required. /// </summary> /// <param name="value"></param> /// <param name="context"></param> /// <param name="level"></param> /// <param name="description"></param> /// <param name="result"></param> public static void ValidateNotRequiredUrl(string value, string context, ViolationLevel level, string description, ViolationCollection result) { if (value != null) { var urlValid = false; try { Uri uri = new Uri(value); urlValid = true; } catch { } if (!urlValid) result.Add(new Violation() { Code = context, ViolationLevel = level, Context = context, Description = description }); } }
/// <summary> /// Validate the Operation. /// </summary> /// <param name="result"></param> public void Validate(string context, ViolationCollection result) { if (result == null) return; if (Method == null || ValidMethods.FirstOrDefault(f => string.Compare(f, Method, false) == 0) == null) { result.Add(new Violation() { Code ="Method", Context = string.Format("{0}Method", context), Description = @"Required. The HTTP method required to invoke this operation. The value MUST be one of the following values: ""GET"", ""HEAD"", ""POST"", ""PUT"", ""PATCH"", ""DELETE"", ""OPTIONS"". The values MUST be in uppercase.", ViolationLevel = ViolationLevel.Error }); } if (!ValidationHelpers.IsValidNickName(NickName)) { result.Add(new Violation() { Code = "NickName", Context = string.Format("{0}NickName", context), Description = @"Required. A unique id for the operation that can be used by tools reading the output for further and easier manipulation. For example, Swagger-Codegen will use the nickname as the method name of the operation in the client it generates. The value MUST be alphanumeric and may include underscores. Whitespace characters are not allowed.", ViolationLevel = ViolationLevel.Error }); } if (Deprecated != null) { if (Deprecated != "false" && Deprecated != "true") { result.Add(new Violation() { Code = "Deprecated", Context = string.Format("{0}Deprecated", context), Description = @"Declares this operation to be deprecated. Usage of the declared operation should be refrained. Valid value MUST be either ""true"" or ""false"". Note: This field will change to type boolean in the future.", ViolationLevel = ViolationLevel.Error }); } } if (Parameters == null) { result.Add(new Violation() { Code = "Parameters", Context = string.Format("{0}Parameters", context), Description = @"Required. The inputs to the operation. If no parameters are needed, an empty array MUST be included.", ViolationLevel = ViolationLevel.Error }); } bool typeValid = true; if (Type == null) typeValid = false; if (typeValid) { var primitives = from T in PrimitiveTypeFormats select T.Value; var knownPrimitive = primitives.FirstOrDefault(f => string.Compare(f.Type, Type, false) == 0 && string.Compare(f.Format, Format, false) == 0) != null; var arrayOrId = !knownPrimitive && string.Compare("array", Type, false) == 0 || Type.Length > 0; if (knownPrimitive) { var match = primitives.FirstOrDefault(f => string.Compare(f.Type, Type, false) == 0 && string.Compare(f.Format, Format, false) == 0); typeValid = (string.Compare(match.Type, Type, false) == 0 && string.Compare(match.Format, Format, false) == 0); } else if (arrayOrId) { if (Format != null) { typeValid = false; } } else { typeValid = false; } } if (!typeValid) { result.Add(new Violation() { Code = "Type", Context = string.Format("{0}Type", context), Description = @"Required (if $ref is not used). The return type of the operation. The value MUST be one of the Primitives, array or a model's id.", ViolationLevel = ViolationLevel.Error }); } if (Items != null) { Items.Validate(string.Format("{0}Items.", context), result); } }
/// <summary> /// Validate the Info object. /// </summary> /// <param name="context"></param> /// <param name="result"></param> public void Validate(string context, ViolationCollection result) { if (Title == null) result.Add(new Violation() { Code = string.Format("{0}Title", context), Context = string.Format("{0}Title", context), ViolationLevel = ViolationLevel.Error, Description = @"Required. The title of the application." }); if (Description == null) result.Add(new Violation() { Code = string.Format("{0}Description", context), Context = string.Format("{0}Description", context), ViolationLevel = ViolationLevel.Error, Description = @"Required. A short description of the application." }); }
protected bool ValidatePropertyIsInCorrectDataType(string context, object value, string propertyName, ViolationCollection result) { try { if (value == null) return true; if (IsInt32) { var v32 = System.Convert.ToInt32(value); } if (IsInt64) { var v32 = System.Convert.ToInt64(value); } if (IsFloat) { var v32 = System.Convert.ToSingle(value); } if (IsDouble) { var v32 = System.Convert.ToDouble(value); } if (IsByte) { var v32 = System.Convert.ToByte(value); } if (propertyName == "DefaultValue") { if (IsBoolean) { var v32 = System.Convert.ToBoolean(value); } } return true; } catch (Exception) { // The property was not in the correct format. result.Add(new Violation() { Code = propertyName, Context = string.Format("{0}{1}", context, propertyName), Description = string.Format(@"The {0} value is not in the correct format based on the Type.", propertyName), ViolationLevel = ViolationLevel.Error }); return false; } }
/// <summary> /// Validate the Api object. /// </summary> /// <param name="result"></param> public void Validate(string context, ViolationCollection result) { if (result == null) return; ValidationHelpers.ValidateRequiredUrl(Path, string.Format("{0}Path", context), @"Required. The relative path to the operation, from the basePath, which this operation describes. The value SHOULD be in a relative (URL) path format.", UriKind.Relative, result); if (Operations != null) { foreach (var operation in Operations) { var c = string.Format(@"{0}Operations[""{1}""].", context, operation.NickName); operation.Validate(c, result); var valid = false; if (operation.IsArray) { if (operation.Items == null) { } else { operation.Items.Validate(c, result); if (operation.Items.IsReference) { var modelId = operation.Items.Reference; if (null == modelId) { // We are invalid. } else { if (Models.Data.ContainsKey(modelId)) { // We are valid. valid = true; } } } } if (!valid) { result.Add(new Violation() { Code = "Items", Context = string.Format("{0}Items.Reference", c), Description = @"Required. The inputs to the operation. If no parameters are needed, an empty array MUST be included.", ViolationLevel = ViolationLevel.Error }); } } } } }
/// <summary> /// Validate the Property. /// </summary> /// <param name="result"></param> public void Validate(string context, ViolationCollection result) { if (null == result) return; var typeValid = true; if (IsReference) { // We cannot validate the refernece from the Property context; the Model context has to validate. } else { if (Type == null) typeValid = false; if (typeValid && !IsReference) { var primitives = from T in PrimitiveTypeFormats select T.Value; var knownPrimitive = primitives.FirstOrDefault(f => string.Compare(f.Type, Type, false) == 0 && string.Compare(f.Format, Format, false) == 0) != null; var arrayOrId = !knownPrimitive && string.Compare("array", Type, false) == 0 || Type.Length > 0; if (knownPrimitive) { var match = primitives.FirstOrDefault(f => string.Compare(f.Type, Type, false) == 0 && string.Compare(f.Format, Format, false) == 0); typeValid = (string.Compare(match.Type, Type, false) == 0 && string.Compare(match.Format, Format, false) == 0); } else if (arrayOrId) { if (Format != null) { typeValid = false; } } else { typeValid = false; } } if (!typeValid) { result.Add(new Violation() { Code = "Type", Context = string.Format("{0}Type", context), Description = @"Required (if $ref is not used). The return type of the operation. The value MUST be one of the Primitives, array or a model's id.", ViolationLevel = ViolationLevel.Error }); } else { typeValid = typeValid && ValidatePropertyIsInCorrectDataType(context, DefaultValue, "DefaultValue", result); typeValid = typeValid && ValidatePropertyIsInCorrectDataType(context, Minimum, "Minimum", result); typeValid = typeValid && ValidatePropertyIsInCorrectDataType(context, Maximum, "Maximum", result); if (typeValid) { typeValid = ValidateDefaultValueIsWithinRange(context, DefaultValue, Minimum, Maximum, result); } } if (typeValid && IsEnum && DefaultValue != null) { var match = Enums.FirstOrDefault(f => string.Compare(f, System.Convert.ToString(DefaultValue), false) == 0); if (match == null) { result.Add(new Violation() { Code = "DefaultValue", Context = string.Format("{0}DefaultValue", context), Description = @"Required (if $ref is not used). The return type of the operation. The value MUST be one of the Primitives, array or a model's id.", ViolationLevel = ViolationLevel.Error }); } } } if (Items != null) { Items.Validate(string.Format("{0}Items.", context), result); } }
protected bool ValidateDefaultValueIsWithinRange(string context, object defaultValue, string minimumValue, string maximumValue, ViolationCollection result) { if (defaultValue == null) return true; try { if (IsInt32) { var d = System.Convert.ToInt32(defaultValue); var min = Int32.MinValue; var max = Int32.MaxValue; if (minimumValue != null) min = System.Convert.ToInt32(minimumValue); if (maximumValue != null) max = System.Convert.ToInt32(maximumValue); if (d < min || d > max) { result.Add(new Violation() { Code = "DefaultValue", Context = string.Format("{0}DefaultValue", context), Description = string.Format(@"The {0} default value is not lie within the Minimum and Maximum range.", "DefaultValue"), ViolationLevel = ViolationLevel.Error }); } return true; } else if (IsInt64) { var d = System.Convert.ToInt64(defaultValue); var min = Int64.MinValue; var max = Int64.MaxValue; if (minimumValue != null) min = System.Convert.ToInt64(minimumValue); if (maximumValue != null) max = System.Convert.ToInt64(maximumValue); if (d < min || d > max) { result.Add(new Violation() { Code = "DefaultValue", Context = string.Format("{0}DefaultValue", context), Description = string.Format(@"The {0} default value is not lie within the Minimum and Maximum range.", "DefaultValue"), ViolationLevel = ViolationLevel.Error }); } return true; } else if (IsFloat) { var d = System.Convert.ToSingle(defaultValue); var min = Single.MinValue; var max = Single.MaxValue; if (minimumValue != null) min = System.Convert.ToSingle(minimumValue); if (maximumValue != null) max = System.Convert.ToSingle(maximumValue); if (d < min || d > max) { result.Add(new Violation() { Code = "DefaultValue", Context = string.Format("{0}DefaultValue", context), Description = string.Format(@"The {0} default value is not lie within the Minimum and Maximum range.", "DefaultValue"), ViolationLevel = ViolationLevel.Error }); } return true; } else if (IsDouble) { var d = System.Convert.ToDouble(defaultValue); var min = Double.MinValue; var max = Double.MaxValue; if (minimumValue != null) min = System.Convert.ToDouble(minimumValue); if (maximumValue != null) max = System.Convert.ToDouble(maximumValue); if (d < min || d > max) { result.Add(new Violation() { Code = "DefaultValue", Context = string.Format("{0}DefaultValue", context), Description = @"A fixed list of possible values. If this field is used in conjunction with the defaultValue field, then the default value MUST be one of the values defined in the enum.", ViolationLevel = ViolationLevel.Error }); } return true; } else { return true; } } catch (Exception) { // This exception should not have been reached - the default, minimum and maximum should already have been validated in a previous call. } return false; }
/// <summary> /// Validate the Resource. /// </summary> /// <param name="result"></param> public void Validate(string context, ViolationCollection result) { if (null == result) return; if (string.IsNullOrEmpty(Path)) result.Add(new Violation() { Code = string.Format("{0}Path", context), Context = string.Format("{0}Path", context), ViolationLevel = ViolationLevel.Error, Description = @"Required. A relative path to the API declaration from the path used to retrieve this Resource Listing. This path does not necessarily have to correspond to the URL which actually serves this resource in the API but rather where the resource listing itself is served. The value SHOULD be in a relative (URL) path format." }); }
/// <summary> /// Validate the Resource. /// </summary> /// <param name="result"></param> public void Validate(ViolationCollection result) { if (null == result) return; if (string.IsNullOrEmpty(SwaggerVersion)) result.Add(new Violation() { Code = "SwaggerVersion", Context = "SwaggerVersion", ViolationLevel = ViolationLevel.Error, Description = @"Required. Specifies the Swagger Specification version being used. It can be used by the Swagger UI and other clients to interpret the API listing. The value MUST be an existing Swagger specification version. Currently, ""1.0"", ""1.1"", ""1.2"" are valid values. The field is a string type for possible non-numeric versions in the future (for example, ""1.2a"")." }); if (Apis == null) result.Add(new Violation() { Code = "Apis", Context = "Apis", ViolationLevel = ViolationLevel.Error, Description = @"Required. Lists the resources to be described by this specification implementation. The array can have 0 or more elements." }); if (Apis != null) { foreach (var api in Apis) { api.Validate(string.Format("Apis[{0}].", Apis.ToList().IndexOf(api)), result); } } if (Info != null) { Info.Validate("Info.", result); } }
/// <summary> /// Validate the ApiDeclaration. /// </summary> /// <param name="result"></param> public void Validate(ViolationCollection result) { if (null == result) return; if (SwaggerVersion != "1.0" && SwaggerVersion != "1.1" && SwaggerVersion != "1.2") result.Add(new Violation() { Code = "SwaggerVersion", Context = "SwaggerVersion", ViolationLevel = ViolationLevel.Error, Description = @"Required. Specifies the Swagger Specification version being used. It can be used by the Swagger UI and other clients to interpret the API listing. The value MUST be an existing Swagger specification version. Currently, ""1.0"", ""1.1"", ""1.2"" are valid values." }); ValidationHelpers.ValidateRequiredUrl(BasePath, "BasePath", @"Required. The root URL serving the API. This field is important because while it is common to have the Resource Listing and API Declarations on the server providing the APIs themselves, it is not a requirement. The API specifications can be served using static files and not generated by the API server itself, so the URL for serving the API cannot always be derived from the URL serving the API specification. The value SHOULD be in the format of a URL.", UriKind.Absolute, result); if (Apis != null) { foreach (var api in Apis) { var index = Apis.ToList().IndexOf(api); api.Validate(string.Format("Apis[{0}].", index), result); } } }
/// <summary> /// Validate an individual tag object. /// </summary> /// <param name="tag"></param> /// <param name="context"></param> /// <param name="nameDescription"></param> /// <param name="result"></param> public static void ValidateTag(TagObject tag, string context, string nameDescription, ViolationCollection result) { if (tag == null) return; var nameValid = tag.Name != null; if (!nameValid) { result.Add(new Violation() { Code = string.Format("{0}.Name", context), Context = string.Format("{0}.Name", context), Description = nameDescription, ViolationLevel = ViolationLevel.Error }); } ValidateExternalDocs(tag.ExternalDocs, string.Format("{0}.ExternalDocs", context), result); }
/// <summary> /// Validate that a Url is required. /// </summary> /// <param name="url"></param> /// <param name="context"></param> /// <param name="description"></param> /// <param name="result"></param> public static void ValidateRequiredUrl(string url, string context, string description, UriKind kind, ViolationCollection result) { if (null == result) return; var valid = false; try { Uri uri = new Uri(url, kind); valid = true; } catch (Exception) { } if (!valid) { result.Add(new Violation() { Code = context, Context = context, ViolationLevel = ViolationLevel.Error, Description = description }); } }
/// <summary> /// Validates the Swagger model and adds any violations to the collection. /// </summary> /// <param name="swagger">The model to validate. Must not be null. </param> /// <param name="result">The collection that will contain any violations. Must not be null. </param> public static void Validate(Swagger swagger, ViolationCollection result) { if (null == swagger) throw new System.ArgumentNullException("swagger"); if (null == result) throw new System.ArgumentNullException("result"); // Swagger if (string.Compare(swagger.Version, "2.0", true) != 0) result.Add(new Violation() { Code = "Swagger", ViolationLevel = ViolationLevel.Error, Context = "Version", Description = @"Specifies the Swagger Specification version being used. It can be used by the Swagger UI and other clients to interpret the API listing. The value MUST be ""2.0"")" }); // Host if (swagger.Host == null || !(swagger.Host.Length > 0)) result.Add(new Violation() { Code = "Host", ViolationLevel = ViolationLevel.Informational, Context = "Host", Description = @"The host (name or ip) serving the API. This MUST be the host only and does not include the scheme nor sub-paths. It MAY include a port. If the host is not included, the host serving the documentation is to be used (including the port). The host does not support path templating." }); // Basepath if (swagger.BasePath == null || !swagger.BasePath.StartsWith("/")) result.Add(new Violation() { Code = "BasePath", ViolationLevel = ViolationLevel.Informational, Context = "BasePath", Description = @"Specifies the Swagger Specification version being used. It can be used by the Swagger UI and other clients to interpret the API listing. The value MUST be ""2.0"")" }); // Schemes var schemesValid = (swagger.Schemes != null); if (schemesValid) { schemesValid = (swagger.Schemes.Count() != 0); if (schemesValid) { var allowed = new string[4] { "http", "https", "ws", "wss" }; foreach (var scheme in swagger.Schemes) { var match = allowed.FirstOrDefault(f => string.Compare(f, scheme, false) == 0); if (match == null) { schemesValid = false; break; } } } } if (!schemesValid) { result.Add(new Violation() { Code = "Schemes", ViolationLevel = ViolationLevel.Informational, Context = "Schemes", Description = @"The transfer protocol of the API. Values MUST be from the list: ""http"", ""https"", ""ws"", ""wss"". If the schemes is not included, the default scheme to be used is the one used to access the specification." }); } // ExternalDocs ValidateExternalDocs(swagger.ExternalDocs, "ExternalDocs", result); // Info var infoValid = (swagger.Info != null); if (!infoValid) result.Add(new Violation() { Code = "Info", ViolationLevel = ViolationLevel.Error, Context = "Info", Description = @"Required Provides the version of the application API (not to be confused by the specification version)." }); if (infoValid) { ValidateRequiredProperty(swagger.Info.Title, "Info.Title", ViolationLevel.Error, @"Required. The title of the application.", result); ValidateRequiredProperty(swagger.Info.Version, "Info.Version", ViolationLevel.Error, @"Required. The title of the application.", result); } // Contact if (swagger.Contact != null) { ValidateNotRequiredUrl(swagger.Contact.Url, "Contact.Url", ViolationLevel.Error, @"The URL pointing to the contact information. MUST be in the format of a URL.", result); } // License if (swagger.License != null) { ValidateRequiredProperty(swagger.License.Name, "License.Name", ViolationLevel.Error, @"Required. The license name used for the API.", result); ValidateNotRequiredUrl(swagger.License.Url, "License.Url", ViolationLevel.Error, @"A URL to the license used for the API. MUST be in the format of a URL.", result); } // Tags if (swagger.Tags != null) { // We need to validate each of the Tags foreach (var tag in swagger.Tags) { var index = swagger.Tags.ToList().IndexOf(tag); ValidateTag(tag, string.Format("Tags[{0}]", index), @"Required. The name of the tag.", result); // See if there is another tag by this name that is not us. var match = swagger.Tags.FirstOrDefault(f => string.Compare(f.Name, tag.Name, true) == 0 && f != tag && swagger.Tags.ToList().IndexOf(f) > index); if (match == null) continue; var context = string.Format("Tags[{0}].Name", swagger.Tags.ToList().IndexOf(match)); result.Add(new Violation() { Code = context, ViolationLevel = ViolationLevel.Error, Context = context, Description = @"Required. The name of the tag. Must be unique. " }); } } }
/// <summary> /// Validate the Parameter. /// </summary> /// <param name="result"></param> public void Validate(ViolationCollection result) { if (null == result) return; if (ParamType == null || ValidParamTypes.FirstOrDefault(f => string.Compare(f, ParamType, false) == 0) == null) { result.Add(new Violation() { Code = "ParamType", Context = "ParamType", Description = @"Required. The type of the parameter (that is, the location of the parameter in the request). The value MUST be one of these values: ""path"", ""query"", ""body"", ""header"", ""form"". Note that the values MUST be lower case.", ViolationLevel = ViolationLevel.Error }); } if (!ValidationHelpers.IsParameterNameValid(null, this)) { result.Add(new Violation() { Code = "Name", Context = "Name", Description = @"Required. The unique name for the parameter. Each name MUST be unique, even if they are associated with different paramType values. Parameter names are case sensitive. If paramType is ""path"", the name field MUST correspond to the associated path segment from the path field in the API Object. If paramType is ""query"", the name field corresponds to the query parameter name. If paramType is ""body"", the name is used only for Swagger-UI and Swagger-Codegen. In this case, the name MUST be ""body"". If paramType is ""form"", the name field corresponds to the form parameter key. If paramType is ""header"", the name field corresponds to the header parameter key.", ViolationLevel = ViolationLevel.Error }); } if (ParamType == "path" && !Required) { result.Add(new Violation() { Code = "Required", Context = "Required", Description = @"A flag to note whether this parameter is required. If this field is not included, it is equivalent to adding this field with the value false. If paramType is ""path"" then this field MUST be included and have the value true.", ViolationLevel = ViolationLevel.Error }); } }
/// <summary> /// Models validation. /// </summary> /// <param name="result"></param> public void Validate(ViolationCollection result) { if (null == result) return; if (null == Data) return; foreach (var pair in Data) { Model model = pair.Value as Model; if (null != model) { model.Validate(string.Format(@"[""{0}""].", pair.Key), result); } bool valid = true; if (pair.Value == null || !(pair.Value is Model)) valid = false; if(valid) { if (string.Compare( (pair.Value as Model).Id, pair.Key, false) != 0) { valid = false; } } if (!valid) { var context = string.Format(@"[""{0}""].Id", pair.Key); result.Add(new Violation() { Code = "Id", Context = context, Description = @"The Models.Id property must be the same as the Model.Id property. ", ViolationLevel = ViolationLevel.Error }); } if (null != model) { if (model.Properties != null && model.Properties.Data != null) { foreach (var propertyPair in model.Properties.Data) { var property = propertyPair.Value as Property; if (null == property) continue; if (property.IsReference) { var refMatchExists = Data.ContainsKey(property.Reference); if (!refMatchExists) { var c = string.Format(@"[""{0}""].Properties[""{1}""].Reference", pair.Key, propertyPair.Key); result.Add(new Violation() { Code = "Reference", Context = c, Description = @"The Models.Id property must be the same as the Model.Id property. ", ViolationLevel = ViolationLevel.Error }); } } if (property.Items != null && property.Items.IsReference) { var refMatchExists = Data.ContainsKey(property.Items.Reference); if (!refMatchExists) { var c = string.Format(@"[""{0}""].Properties[""{1}""].Items.Reference", pair.Key, propertyPair.Key); result.Add(new Violation() { Code = "Reference", Context = c, Description = @"Required (if type is not used). The Model to be used. The value MUST be a model's id. ", ViolationLevel = ViolationLevel.Error }); } } } } if (model.SubTypes != null) { var description = @"List of the model ids that inherit from this model. Sub models inherit all the properties of the parent model. Since inheritance is transitive, if the parent of a model inherits from another model, its sub-model will include all properties. As such, if you have Foo->Bar->Baz, then Baz will inherit the properties of Bar and Foo. There MUST NOT be a cyclic definition of inheritance. For example, if Foo -> ... -> Bar, having Bar -> ... -> Foo is not allowed. There also MUST NOT be a case of multiple inheritance. For example, Foo -> Baz <- Bar is not allowed. A sub-model definition MUST NOT override the properties of any of its ancestors. All sub-models MUST be defined in the same API Declaration."; foreach (var subType in model.SubTypes) { var isMe = string.Compare(subType, model.Id, false) == 0; if (isMe) { var self = string.Format("A SubType cannot reference the same model. \r\n\r\n{0}", description); var c = string.Format(@"[""{0}""].SubTypes[""{1}""]", model.Id, subType); result.Add(new Violation() { Code = "SubTypes", Context = c, Description = self, ViolationLevel = ViolationLevel.Error }); continue; } if (subType == null || !Data.ContainsKey(subType)) { var missing = string.Format("A SubType must reference an existing model. \r\n\r\n{0}", description); var c = string.Format(@"[""{0}""].SubTypes[""{1}""]", model.Id, subType); result.Add(new Violation() { Code = "SubTypes", Context = c, Description = missing, ViolationLevel = ViolationLevel.Error }); } if (subType != null) { var subTypeModel = SubtypesMe(model.Id); if (subTypeModel != null) { // This means that a subtype (or a subtype of a subtype) is referencing me. A circular reference is not allowed. var c = string.Format(@"[""{1}""].SubTypes[""{0}""]", subTypeModel, model.Id); result.Add(new Violation() { Code = "SubTypes", Context = c, Description = "A circular reference is not allowed. A SubType of a model either references the model or one or its descendents. ", ViolationLevel = ViolationLevel.Error }); break; } } } } } } }
/// <summary> /// Validate the Property. /// </summary> /// <param name="result"></param> public void Validate(string context, ViolationCollection result) { if (null == result) return; var typeValid = true; if (Type == null && !IsReference) typeValid = false; if (typeValid) { var primitives = from T in PrimitiveTypeFormats select T.Value; var knownPrimitive = primitives.FirstOrDefault(f => string.Compare(f.Type, Type, false) == 0 && string.Compare(f.Format, Format, false) == 0) != null; var isId = !knownPrimitive && IsReference; if (knownPrimitive) { var match = primitives.FirstOrDefault(f => string.Compare(f.Type, Type, false) == 0 && string.Compare(f.Format, Format, false) == 0); typeValid = (string.Compare(match.Type, Type, false) == 0 && string.Compare(match.Format, Format, false) == 0); } else if (isId) { } else { typeValid = false; } } if (!typeValid) { result.Add(new Violation() { Code = "Type", Context = string.Format("{0}Type", context), Description = @"Required (if $ref is not used). The return type of the operation. The value MUST be one of the Primitives, array or a model's id.", ViolationLevel = ViolationLevel.Error }); } }