/// <summary> /// Compare a modified document node (this) to a previous one and look for breaking as well as non-breaking changes. /// </summary> /// <param name="context">The modified document context.</param> /// <param name="previous">The original document model.</param> /// <returns>A list of messages from the comparison.</returns> public override IEnumerable<ComparisonMessage> Compare(ComparisonContext context, SwaggerBase previous) { var priorResponse = previous as OperationResponse; if (priorResponse == null) { throw new ArgumentNullException("previous"); } if (context == null) { throw new ArgumentNullException("context"); } context.Direction = DataDirection.Response; base.Compare(context, previous); var headers = Headers != null ? Headers : new Dictionary<string, Header>(); var priorHeaders = priorResponse.Headers != null ? priorResponse.Headers : new Dictionary<string, Header>(); foreach (var header in headers) { context.Push(header.Key); Header oldHeader = null; if (!priorHeaders.TryGetValue(header.Key, out oldHeader)) { context.LogInfo(ComparisonMessages.AddingHeader, header.Key); } else { header.Value.Compare(context, oldHeader); } context.Pop(); } foreach (var header in priorHeaders) { context.Push(header.Key); Header newHeader = null; if (!headers.TryGetValue(header.Key, out newHeader)) { context.LogBreakingChange(ComparisonMessages.RemovingHeader, header.Key); } context.Pop(); } if (Schema != null && priorResponse.Schema != null) { Schema.Compare(context, priorResponse.Schema); } context.Direction = DataDirection.None; return context.Messages; }
/// <summary> /// Compare a modified document node (this) to a previous one and look for breaking as well as non-breaking changes. /// </summary> /// <param name="context">The modified document context.</param> /// <param name="previous">The original document model.</param> /// <returns>A list of messages from the comparison.</returns> public virtual IEnumerable<ComparisonMessage> Compare(ComparisonContext context, SwaggerBase previous) { if (previous == null) throw new ArgumentNullException("previous"); if (context == null) throw new ArgumentNullException("context"); yield break; }
/// <summary> /// Compare a modified document node (this) to a previous one and look for breaking as well as non-breaking changes. /// </summary> /// <param name="context">The modified document context.</param> /// <param name="previous">The original document model.</param> /// <returns>A list of messages from the comparison.</returns> public override IEnumerable<ComparisonMessage> Compare(ComparisonContext context, SwaggerBase previous) { var priorParameter = previous as SwaggerParameter; if (priorParameter == null) { throw new ArgumentNullException("priorVersion"); } if (context == null) { throw new ArgumentNullException("context"); } context.Direction = DataDirection.Request; base.Compare(context, previous); if (In != priorParameter.In) { context.LogBreakingChange(ComparisonMessages.ParameterInHasChanged, priorParameter.In.ToString().ToLower(CultureInfo.CurrentCulture), In.ToString().ToLower(CultureInfo.CurrentCulture)); } if (this.IsConstant != priorParameter.IsConstant) { context.LogBreakingChange(ComparisonMessages.ConstantStatusHasChanged); } if (Reference != null && !Reference.Equals(priorParameter.Reference)) { context.LogBreakingChange(ComparisonMessages.ReferenceRedirection); } if (Schema != null && priorParameter.Schema != null) { Schema.Compare(context, priorParameter.Schema); } context.Direction = DataDirection.None; return context.Messages; }
private void CompareProperties(ComparisonContext context, SwaggerObject prior) { // Additional properties if (prior.AdditionalProperties == null && AdditionalProperties != null) { context.LogBreakingChange(ComparisonMessages.AddedAdditionalProperties); } else if (prior.AdditionalProperties != null && AdditionalProperties == null) { context.LogBreakingChange(ComparisonMessages.RemovedAdditionalProperties); } else if (AdditionalProperties != null) { context.Push("additionalProperties"); AdditionalProperties.Compare(context, prior.AdditionalProperties); context.Pop(); } }
private void CompareEnums(ComparisonContext context, SwaggerObject prior) { if (prior.Enum == null && this.Enum == null) return; bool relaxes = (prior.Enum != null && this.Enum == null); bool constrains = (prior.Enum == null && this.Enum != null); if (!relaxes && !constrains) { // 1. Look for removed elements (constraining). constrains = prior.Enum.Any(str => !this.Enum.Contains(str)); // 2. Look for added elements (relaxing). relaxes = this.Enum.Any(str => !prior.Enum.Contains(str)); } if (context.Direction == DataDirection.Request) { if (constrains) { context.LogBreakingChange(relaxes ? ComparisonMessages.ConstraintChanged : ComparisonMessages.ConstraintIsStronger, "enum"); return; } } else if (context.Direction == DataDirection.Response) { if (relaxes) { context.LogBreakingChange(constrains ? ComparisonMessages.ConstraintChanged : ComparisonMessages.ConstraintIsWeaker, "enum"); return; } } if (relaxes && constrains) context.LogInfo(ComparisonMessages.ConstraintChanged, "enum"); else if (relaxes || constrains) context.LogInfo(relaxes ? ComparisonMessages.ConstraintIsWeaker : ComparisonMessages.ConstraintIsStronger, "enum"); }
protected void CompareItems(ComparisonContext context, SwaggerObject prior) { if (prior == null) { throw new ArgumentNullException("prior"); } if (context == null) { throw new ArgumentNullException("context"); } if (prior.Items != null && Items != null) { context.Push("items"); Items.Compare(context, prior.Items); context.Pop(); } }
protected void CompareFormats(ComparisonContext context, SwaggerObject prior) { if (prior == null) { throw new ArgumentNullException("prior"); } if (context == null) { throw new ArgumentNullException("context"); } if (prior.Format == null && Format != null || prior.Format != null && Format == null || prior.Format != null && Format != null && !prior.Format.Equals(Format)) { context.LogBreakingChange(ComparisonMessages.TypeFormatChanged); } }
protected void CompareConstraints(ComparisonContext context, SwaggerObject prior) { if (prior == null) { throw new ArgumentNullException("prior"); } if (context == null) { throw new ArgumentNullException("context"); } if ((prior.MultipleOf == null && MultipleOf != null) || (prior.MultipleOf != null && !prior.MultipleOf.Equals(MultipleOf))) { context.LogBreakingChange(ComparisonMessages.ConstraintChanged, "multipleOf"); } if ((prior.Maximum == null && Maximum != null) || (prior.Maximum != null && !prior.Maximum.Equals(Maximum)) || prior.ExclusiveMaximum != ExclusiveMaximum) { // Flag stricter constraints for requests and relaxed constraints for responses. if (prior.ExclusiveMaximum != ExclusiveMaximum || context.Direction == DataDirection.None) context.LogBreakingChange(ComparisonMessages.ConstraintChanged, "maximum"); else if (context.Direction == DataDirection.Request && Narrows(prior.Maximum, Maximum, true)) context.LogBreakingChange(ComparisonMessages.ConstraintIsStronger, "maximum"); else if (context.Direction == DataDirection.Response && Widens(prior.Maximum, Maximum, true)) context.LogBreakingChange(ComparisonMessages.ConstraintIsWeaker, "maximum"); else if (Narrows(prior.Maximum, Maximum, false)) context.LogInfo(ComparisonMessages.ConstraintIsStronger, "maximum"); else if (Widens(prior.Maximum, Maximum, false)) context.LogInfo(ComparisonMessages.ConstraintIsWeaker, "maximum"); } if ((prior.Minimum == null && Minimum != null) || (prior.Minimum != null && !prior.Minimum.Equals(Minimum)) || prior.ExclusiveMinimum != ExclusiveMinimum) { // Flag stricter constraints for requests and relaxed constraints for responses. if (prior.ExclusiveMinimum != ExclusiveMinimum || context.Direction == DataDirection.None) context.LogBreakingChange(ComparisonMessages.ConstraintChanged, "minimum"); else if (context.Direction == DataDirection.Request && Narrows(prior.Minimum, Minimum, true)) context.LogBreakingChange(ComparisonMessages.ConstraintIsStronger, "minimum"); else if (context.Direction == DataDirection.Response && Widens(prior.Minimum, Minimum, true)) context.LogBreakingChange(ComparisonMessages.ConstraintIsWeaker, "minimum"); else if (Narrows(prior.Minimum, Minimum, false)) context.LogInfo(ComparisonMessages.ConstraintIsStronger, "minimum"); else if (Widens(prior.Minimum, Minimum, false)) context.LogInfo(ComparisonMessages.ConstraintIsWeaker, "minimum"); } if ((prior.MaxLength == null && MaxLength != null) || (prior.MaxLength != null && !prior.MaxLength.Equals(MaxLength))) { // Flag stricter constraints for requests and relaxed constraints for responses. if (context.Direction == DataDirection.None) context.LogBreakingChange(ComparisonMessages.ConstraintChanged, "maxLength"); else if (context.Direction == DataDirection.Request && Narrows(prior.MaxLength, MaxLength, false)) context.LogBreakingChange(ComparisonMessages.ConstraintIsStronger, "maxLength"); else if (context.Direction == DataDirection.Response && Widens(prior.MaxLength, MaxLength, false)) context.LogBreakingChange(ComparisonMessages.ConstraintIsWeaker, "maxLength"); else if (Narrows(prior.MaxLength, MaxLength, false)) context.LogInfo(ComparisonMessages.ConstraintIsStronger, "maxLength"); else if (Widens(prior.MaxLength, MaxLength, false)) context.LogInfo(ComparisonMessages.ConstraintIsWeaker, "maxLength"); } if ((prior.MinLength == null && MinLength != null) || (prior.MinLength != null && !prior.MinLength.Equals(MinLength))) { // Flag stricter constraints for requests and relaxed constraints for responses. if (context.Direction == DataDirection.None) context.LogBreakingChange(ComparisonMessages.ConstraintChanged, "minLength"); else if (context.Direction == DataDirection.Request && Narrows(prior.MinLength, MinLength, true)) context.LogBreakingChange(ComparisonMessages.ConstraintIsStronger, "minimum"); else if (context.Direction == DataDirection.Response && Widens(prior.MinLength, MinLength, true)) context.LogBreakingChange(ComparisonMessages.ConstraintIsWeaker, "minimum"); else if (Narrows(prior.MinLength, MinLength, false)) context.LogInfo(ComparisonMessages.ConstraintIsStronger, "minLength"); else if (Widens(prior.MinLength, MinLength, false)) context.LogInfo(ComparisonMessages.ConstraintIsWeaker, "minLength"); } if ((prior.Pattern == null && Pattern != null) || (prior.Pattern != null && !prior.Pattern.Equals(Pattern))) { context.LogBreakingChange(ComparisonMessages.ConstraintChanged, "pattern"); } if ((prior.MaxItems == null && MaxItems != null) || (prior.MaxItems != null && !prior.MaxItems.Equals(MaxItems))) { // Flag stricter constraints for requests and relaxed constraints for responses. if (context.Direction == DataDirection.None) context.LogBreakingChange(ComparisonMessages.ConstraintChanged, "maxItems"); else if (context.Direction == DataDirection.Request && Narrows(prior.MaxItems, MaxItems, false)) context.LogBreakingChange(ComparisonMessages.ConstraintIsStronger, "maxItems"); else if (context.Direction == DataDirection.Response && Widens(prior.MaxItems, MaxItems, false)) context.LogBreakingChange(ComparisonMessages.ConstraintIsWeaker, "maxItems"); else if (Narrows(prior.MaxItems, MaxItems, false)) context.LogInfo(ComparisonMessages.ConstraintIsStronger, "maxItems"); else if (Widens(prior.MaxItems, MaxItems, false)) context.LogInfo(ComparisonMessages.ConstraintIsWeaker, "maxItems"); } if ((prior.MinItems == null && MinItems != null) || (prior.MinItems != null && !prior.MinItems.Equals(MinItems))) { // Flag stricter constraints for requests and relaxed constraints for responses. if (context.Direction == DataDirection.None) context.LogBreakingChange(ComparisonMessages.ConstraintChanged, "minItems"); else if (context.Direction == DataDirection.Request && Narrows(prior.MinItems, MinItems, true)) context.LogBreakingChange(ComparisonMessages.ConstraintIsStronger, "minItems"); else if (context.Direction == DataDirection.Response && Widens(prior.MinItems, MinItems, true)) context.LogBreakingChange(ComparisonMessages.ConstraintIsWeaker, "minItems"); else if (Narrows(prior.MinItems, MinItems, true)) context.LogInfo(ComparisonMessages.ConstraintIsStronger, "minItems"); else if (Widens(prior.MinItems, MinItems, true)) context.LogInfo(ComparisonMessages.ConstraintIsWeaker, "minItems"); } if (prior.UniqueItems != UniqueItems) { context.LogBreakingChange(ComparisonMessages.ConstraintChanged, "uniqueItems"); } }
/// <summary> /// Compare a modified document node (this) to a previous one and look for breaking as well as non-breaking changes. /// </summary> /// <param name="context">The modified document context.</param> /// <param name="previous">The original document model.</param> /// <returns>A list of messages from the comparison.</returns> public override IEnumerable<ComparisonMessage> Compare(ComparisonContext context, SwaggerBase previous) { var priorSchema = previous as Schema; if (priorSchema == null) { throw new ArgumentNullException("priorVersion"); } if (context == null) { throw new ArgumentNullException("context"); } int referenced = 0; var thisSchema = this; if (!string.IsNullOrWhiteSpace(thisSchema.Reference)) { thisSchema = FindReferencedSchema(thisSchema.Reference, (context.CurrentRoot as ServiceDefinition).Definitions); referenced += 1; } if (!string.IsNullOrWhiteSpace(priorSchema.Reference)) { priorSchema = FindReferencedSchema(priorSchema.Reference, (context.PreviousRoot as ServiceDefinition).Definitions); referenced += 1; } // Avoid doing the comparison repeatedly by marking for which direction it's already been done. if (context.Direction != DataDirection.None && referenced == 2) { // Comparing two referenced schemas in the context of a parameter or response -- did we already do this? if (thisSchema._compareDirection == context.Direction || thisSchema._compareDirection == DataDirection.Both) { return new ComparisonMessage[0]; } _compareDirection |= context.Direction; } if (thisSchema != this || priorSchema != previous) { return thisSchema.Compare(context, priorSchema); } base.Compare(context, previous); if (priorSchema.ReadOnly != ReadOnly) { context.LogBreakingChange(ComparisonMessages.ReadonlyPropertyChanged2, priorSchema.ReadOnly.ToString().ToLower(CultureInfo.CurrentCulture), ReadOnly.ToString().ToLower(CultureInfo.CurrentCulture)); } if ((priorSchema.Discriminator == null && Discriminator != null) || (priorSchema.Discriminator != null && !priorSchema.Discriminator.Equals(Discriminator))) { context.LogBreakingChange(ComparisonMessages.DifferentDiscriminator); } if ((priorSchema.Extends == null && Extends != null) || (priorSchema.Extends != null && !priorSchema.Extends.Equals(Extends))) { context.LogBreakingChange(ComparisonMessages.DifferentExtends); } if ((priorSchema.AllOf == null && AllOf != null) || (priorSchema.AllOf != null && AllOf == null)) { context.LogBreakingChange(ComparisonMessages.DifferentAllOf); } else if (priorSchema.AllOf != null) { CompareAllOfs(context, priorSchema); } context.Push("properties"); CompareProperties(context, priorSchema); context.Pop(); return context.Messages; }
/// <summary> /// Since some services may rely on semantic versioning, comparing versions is fairly complex. /// </summary> /// <param name="context">A comparison context.</param> /// <param name="newVer">The new version string.</param> /// <param name="oldVer">The old version string</param> /// <remarks> /// In semantic versioning schemes, only the major and minor version numbers are considered when comparing versions. /// Build numbers are ignored. /// </remarks> private void CompareVersions(ComparisonContext context, string newVer, string oldVer) { var oldVersion = oldVer.Split('.'); var newVersion = newVer.Split('.'); // If the version consists only of numbers separated by '.', we'll consider it semantic versioning. if (!context.Strict && oldVersion.Length > 0 && newVersion.Length > 0) { bool versionChanged = false; // Situation 1: The versioning scheme is semantic, i.e. it uses a major.minr.build-number scheme, where each component is an integer. // In this case, we care about the major/minor numbers, but not the build number. In othe words, ifall that is different // is the build number, it will not be treated as a version change. int oldMajor = 0, newMajor = 0; bool integers = int.TryParse(oldVersion[0], out oldMajor) && int.TryParse(newVersion[0], out newMajor); if (integers && oldMajor != newMajor) { versionChanged = true; if (oldMajor > newMajor) { context.LogError(ComparisonMessages.VersionsReversed, oldVer, newVer); } } if (!versionChanged && integers && oldVersion.Length > 1 && newVersion.Length > 1) { int oldMinor = 0, newMinor = 0; integers = int.TryParse(oldVersion[1], out oldMinor) && int.TryParse(newVersion[1], out newMinor); if (integers && oldMinor != newMinor) { versionChanged = true; if (oldMinor > newMinor) { context.LogError(ComparisonMessages.VersionsReversed, oldVer, newVer); } } } // Situation 2: The versioning scheme is something else, maybe a date or just a label? // Regardless of what it is, we just check whether the two strings are equal or not. if (!versionChanged && !integers) { versionChanged = !oldVer.ToLower(CultureInfo.CurrentCulture).Equals(newVer.ToLower(CultureInfo.CurrentCulture)); } context.Strict = !versionChanged; } }
/// <summary> /// Compare a modified document node (this) to a previous one and look for breaking as well as non-breaking changes. /// </summary> /// <param name="context">The modified document context.</param> /// <param name="previous">The original document model.</param> /// <returns>A list of messages from the comparison.</returns> public override IEnumerable<ComparisonMessage> Compare(ComparisonContext context, SwaggerBase previous) { if (previous == null) throw new ArgumentNullException("previous"); context.CurrentRoot = this; context.PreviousRoot = previous; context.Push("#"); base.Compare(context, previous); var previousDefinition = previous as ServiceDefinition; if (previousDefinition == null) throw new ArgumentException("Comparing a service definition with something else."); if (Info != null && previousDefinition.Info != null) { context.Push("info/version"); CompareVersions(context, Info.Version, previousDefinition.Info.Version); context.Pop(); } if (context.Strict) { // There was no version change between the documents. This is not an error, but noteworthy. context.LogInfo(ComparisonMessages.NoVersionChange); } // Check that all the protocols of the old version are supported by the new version. context.Push("schemes"); foreach (var scheme in previousDefinition.Schemes) { if (!Schemes.Contains(scheme)) { context.LogBreakingChange(ComparisonMessages.ProtocolNoLongerSupported, scheme); } } context.Pop(); // Check that all the request body formats that were accepted still are. context.Push("consumes"); foreach (var format in previousDefinition.Consumes) { if (!Consumes.Contains(format)) { context.LogBreakingChange(ComparisonMessages.RequestBodyFormatNoLongerSupported, format); } } context.Pop(); // Check that all the response body formats were also supported by the old version. context.Push("produces"); foreach (var format in Produces) { if (!previousDefinition.Produces.Contains(format)) { context.LogBreakingChange(ComparisonMessages.ResponseBodyFormatNowSupported, format); } } context.Pop(); // Check that no paths were removed, and compare the paths that are still there. var newPaths = RemovePathVariables(Paths); context.Push("paths"); foreach (var path in previousDefinition.Paths.Keys) { var p = Regex.Replace(path, @"\{\w*\}", @"{}"); context.Push(path); Dictionary<string, Operation> operations = null; if (!newPaths.TryGetValue(p, out operations)) { context.LogBreakingChange(ComparisonMessages.RemovedPath, path); } else { Dictionary<string, Operation> previousOperations = previousDefinition.Paths[path]; foreach (var previousOperation in previousOperations) { Operation newOperation = null; if (!operations.TryGetValue(previousOperation.Key, out newOperation)) { context.LogBreakingChange(ComparisonMessages.RemovedOperation, previousOperation.Value.OperationId); } } foreach (var operation in operations) { Operation previousOperation = null; if (previousDefinition.Paths[path].TryGetValue(operation.Key, out previousOperation)) { context.Push(operation.Key); operation.Value.Compare(context, previousOperation); context.Pop(); } } } context.Pop(); } context.Pop(); newPaths = RemovePathVariables(CustomPaths); context.Push("x-ms-paths"); foreach (var path in previousDefinition.CustomPaths.Keys) { var p = Regex.Replace(path, @"\{\w*\}", @"{}"); context.Push(path); Dictionary<string, Operation> operations = null; if (!newPaths.TryGetValue(p, out operations)) { context.LogBreakingChange(ComparisonMessages.RemovedPath, path); } else { Dictionary<string, Operation> previousOperations = previousDefinition.CustomPaths[path]; foreach (var previousOperation in previousOperations) { Operation newOperation = null; if (!operations.TryGetValue(previousOperation.Key, out newOperation)) { context.LogBreakingChange(ComparisonMessages.RemovedOperation, previousOperation.Value.OperationId); } } foreach (var operation in operations) { Operation previousOperation = null; if (previousDefinition.CustomPaths[path].TryGetValue(operation.Key, out previousOperation)) { context.Push(operation.Key); operation.Value.Compare(context, previousOperation); context.Pop(); } } } context.Pop(); } context.Pop(); ReferenceTrackSchemas(this); ReferenceTrackSchemas(previousDefinition); context.Push("parameters"); foreach (var def in previousDefinition.Parameters.Keys) { SwaggerParameter parameter = null; if (!Parameters.TryGetValue(def, out parameter)) { context.LogBreakingChange(ComparisonMessages.RemovedClientParameter, def); } else { context.Push(def); parameter.Compare(context, previousDefinition.Parameters[def]); context.Pop(); } } context.Pop(); context.Push("responses"); foreach (var def in previousDefinition.Responses.Keys) { OperationResponse response = null; if (!Responses.TryGetValue(def, out response)) { context.LogBreakingChange(ComparisonMessages.RemovedDefinition, def); } else { context.Push(def); response.Compare(context, previousDefinition.Responses[def]); context.Pop(); } } context.Pop(); context.Push("definitions"); foreach (var def in previousDefinition.Definitions.Keys) { Schema schema = null; Schema oldSchema = previousDefinition.Definitions[def]; if (!Definitions.TryGetValue(def, out schema)) { if (oldSchema.IsReferenced) // It's only an error if the definition is referenced in the old service. context.LogBreakingChange(ComparisonMessages.RemovedDefinition, def); } else if (schema.IsReferenced && oldSchema.IsReferenced) { context.Push(def); schema.Compare(context, previousDefinition.Definitions[def]); context.Pop(); } } context.Pop(); context.Pop(); return context.Messages; }
private void CheckParameters(ComparisonContext context, Operation priorOperation) { // Check that no parameters were removed or reordered, and compare them if it's not the case. var currentRoot = (context.CurrentRoot as ServiceDefinition); var previousRoot = (context.PreviousRoot as ServiceDefinition); foreach (var oldParam in priorOperation.Parameters .Select(p => string.IsNullOrEmpty(p.Reference) ? p : FindReferencedParameter(p.Reference, previousRoot.Parameters))) { SwaggerParameter newParam = FindParameter(oldParam.Name, Parameters, currentRoot.Parameters); context.Push(oldParam.Name); if (newParam != null) { newParam.Compare(context, oldParam); } else if (oldParam.IsRequired) { context.LogBreakingChange(ComparisonMessages.RemovedRequiredParameter, oldParam.Name); } context.Pop(); } // Check that no required parameters were added. foreach (var newParam in Parameters .Select(p => string.IsNullOrEmpty(p.Reference) ? p : FindReferencedParameter(p.Reference, currentRoot.Parameters)) .Where(p => p != null && p.IsRequired)) { if (newParam == null) continue; SwaggerParameter oldParam = FindParameter(newParam.Name, priorOperation.Parameters, previousRoot.Parameters); if (oldParam == null) { context.Push(newParam.Name); context.LogBreakingChange(ComparisonMessages.AddingRequiredParameter, newParam.Name); context.Pop(); } } }
/// <summary> /// Compare a modified document node (this) to a previous one and look for breaking as well as non-breaking changes. /// </summary> /// <param name="context">The modified document context.</param> /// <param name="previous">The original document model.</param> /// <returns>A list of messages from the comparison.</returns> public override IEnumerable<ComparisonMessage> Compare(ComparisonContext context, SwaggerBase previous) { var priorOperation = previous as Operation; var currentRoot = (context.CurrentRoot as ServiceDefinition); var previousRoot = (context.PreviousRoot as ServiceDefinition); if (priorOperation == null) { throw new ArgumentException("previous"); } base.Compare(context, previous); if (!OperationId.Equals(priorOperation.OperationId)) { context.LogBreakingChange(ComparisonMessages.ModifiedOperationId); } CheckParameters(context, priorOperation); if (Responses != null && priorOperation.Responses != null) { foreach (var response in Responses) { var oldResponse = priorOperation.FindResponse(response.Key, priorOperation.Responses); context.Push(response.Key); if (oldResponse == null) { context.LogBreakingChange(ComparisonMessages.AddingResponseCode, response.Key); } else { response.Value.Compare(context, oldResponse); } context.Pop(); } foreach (var response in priorOperation.Responses) { var newResponse = this.FindResponse(response.Key, this.Responses); if (newResponse == null) { context.Push(response.Key); context.LogBreakingChange(ComparisonMessages.RemovedResponseCode, response.Key); context.Pop(); } } } return context.Messages; }
private void CompareProperties(ComparisonContext context, Schema priorSchema) { // Were any properties removed? if (priorSchema.Properties != null) { foreach (var def in priorSchema.Properties) { Schema model = null; if (Properties == null || !Properties.TryGetValue(def.Key, out model)) { context.LogBreakingChange(ComparisonMessages.RemovedProperty1, def.Key); } else { context.Push(def.Key); model.Compare(context, def.Value); context.Pop(); } } } // Were any required properties added? if (Properties != null) { foreach (var def in Properties.Keys) { Schema model = null; if (priorSchema.Properties == null || !priorSchema.Properties.TryGetValue(def, out model) && Required.Contains(def)) { context.LogBreakingChange(ComparisonMessages.AddedRequiredProperty1, def); } } } }
private void CompareAllOfs(ComparisonContext context, Schema priorSchema) { var different = 0; foreach (var schema in priorSchema.AllOf) { if (!AllOf.Select(s => s.Reference).ToArray().Contains(schema.Reference)) { different += 1; } } foreach (var schema in AllOf) { if (!priorSchema.AllOf.Select(s => s.Reference).ToArray().Contains(schema.Reference)) { different += 1; } } if (different > 0) { context.LogBreakingChange(ComparisonMessages.DifferentAllOf); } }
public override IEnumerable<ComparisonMessage> Compare() { Logger.LogInfo(Resources.ParsingSwagger); if (string.IsNullOrWhiteSpace(Settings.Input) || string.IsNullOrWhiteSpace(Settings.Previous)) { throw ErrorManager.CreateError(Resources.InputRequired); } var oldDefintion = SwaggerParser.Load(Settings.Previous, Settings.FileSystem); var newDefintion = SwaggerParser.Load(Settings.Input, Settings.FileSystem); var context = new ComparisonContext(oldDefintion, newDefintion); // Look for semantic errors and warnings in the new document. var validator = new RecursiveObjectValidator(PropertyNameResolver.JsonName); var validationMessages = validator.GetValidationExceptions(newDefintion).ToList(); // Only compare versions if the new version is correct. var comparisonMessages = !validationMessages.Any(m => m.Severity > LogEntrySeverity.Error) ? newDefintion.Compare(context, oldDefintion) : Enumerable.Empty<ComparisonMessage>(); return validationMessages .Select(msg => new ComparisonMessage(new MessageTemplate { Id = 0, Message = msg.Message }, string.Join("/", msg.Path), msg.Severity)) .Concat(comparisonMessages); }
/// <summary> /// Compare a modified document node (this) to a previous one and look for breaking as well as non-breaking changes. /// </summary> /// <param name="context">The modified document context.</param> /// <param name="previous">The original document model.</param> /// <returns>A list of messages from the comparison.</returns> public override IEnumerable<ComparisonMessage> Compare(ComparisonContext context, SwaggerBase previous) { var prior = previous as SwaggerObject; if (prior == null) { throw new ArgumentNullException("priorVersion"); } if (context == null) { throw new ArgumentNullException("context"); } base.Compare(context, previous); if (Reference != null && !Reference.Equals(prior.Reference)) { context.LogBreakingChange(ComparisonMessages.ReferenceRedirection); } if (IsRequired != prior.IsRequired) { if (context.Direction != DataDirection.Response) { if (IsRequired && !prior.IsRequired) { context.LogBreakingChange(ComparisonMessages.RequiredStatusChange); } else { context.LogInfo(ComparisonMessages.RequiredStatusChange); } } } // Are the types the same? if (prior.Type.HasValue != Type.HasValue || (Type.HasValue && prior.Type.Value != Type.Value)) { context.LogBreakingChange(ComparisonMessages.TypeChanged); } // What about the formats? CompareFormats(context, prior); CompareItems(context, prior); if (Default != null && !Default.Equals(prior.Default) || (Default == null && !string.IsNullOrEmpty(prior.Default))) { context.LogBreakingChange(ComparisonMessages.DefaultValueChanged); } if (Type.HasValue && Type.Value == DataType.Array && prior.CollectionFormat != CollectionFormat) { context.LogBreakingChange(ComparisonMessages.ArrayCollectionFormatChanged); } CompareConstraints(context, prior); CompareProperties(context, prior); CompareEnums(context, prior); return context.Messages; }