Пример #1
0
        /// <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;
        }
Пример #2
0
        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");
        }
Пример #3
0
        /// <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;
        }
Пример #4
0
        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");
            }
        }
Пример #5
0
        /// <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;
        }