/// <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;

            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.PushProperty("info");
                context.PushProperty("version");

                CompareVersions(context, Info.Version, previousDefinition.Info.Version);

                context.Pop();
                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.PushProperty("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.PushProperty("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.PushProperty("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.PushProperty("paths");
            foreach (var path in previousDefinition.Paths.Keys)
            {
                var p = Regex.Replace(path, @"\{\w*\}", @"{}");

                context.PushProperty(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.PushProperty(operation.Key);
                            operation.Value.Compare(context, previousOperation);
                            context.Pop();
                        }
                    }
                }
                context.Pop();
            }
            context.Pop();

            newPaths = RemovePathVariables(CustomPaths);

            context.PushProperty("x-ms-paths");
            foreach (var path in previousDefinition.CustomPaths.Keys)
            {
                var p = Regex.Replace(path, @"\{\w*\}", @"{}");

                context.PushProperty(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.PushProperty(operation.Key);
                            operation.Value.Compare(context, previousOperation);
                            context.Pop();
                        }
                    }
                }
                context.Pop();
            }
            context.Pop();

            ReferenceTrackSchemas(this);
            ReferenceTrackSchemas(previousDefinition);

            context.PushProperty("parameters");
            foreach (var def in previousDefinition.Parameters.Keys)
            {
                SwaggerParameter parameter = null;
                if (!Parameters.TryGetValue(def, out parameter))
                {
                    context.LogBreakingChange(ComparisonMessages.RemovedClientParameter, def);
                }
                else
                {
                    context.PushProperty(def);
                    parameter.Compare(context, previousDefinition.Parameters[def]);
                    context.Pop();
                }
            }
            context.Pop();

            context.PushProperty("responses");
            foreach (var def in previousDefinition.Responses.Keys)
            {
                OperationResponse response = null;
                if (!Responses.TryGetValue(def, out response))
                {
                    context.LogBreakingChange(ComparisonMessages.RemovedDefinition, def);
                }
                else
                {
                    context.PushProperty(def);
                    response.Compare(context, previousDefinition.Responses[def]);
                    context.Pop();
                }
            }
            context.Pop();

            context.PushProperty("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.PushProperty(def);
                    schema.Compare(context, previousDefinition.Definitions[def]);
                    context.Pop();
                }
            }
            context.Pop();

            context.Pop();

            return(context.Messages);
        }
Ejemplo n.º 2
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 <ServiceDefinition> context,
            Operation previous
            )
        {
            var priorOperation = previous;

            var currentRoot  = context.CurrentRoot;
            var previousRoot = context.PreviousRoot;

            if (priorOperation == null)
            {
                throw new ArgumentException("previous");
            }

            base.Compare(context, previous);

            if (OperationId != priorOperation.OperationId)
            {
                context.LogBreakingChange(ComparisonMessages.ModifiedOperationId, priorOperation.OperationId, OperationId);
            }
            Extensions.TryGetValue("x-ms-long-running-operation", out var currentLongrunningOperationValue);
            priorOperation.Extensions.TryGetValue("x-ms-long-running-operation", out var priorLongrunningOperationValue);

            currentLongrunningOperationValue = currentLongrunningOperationValue == null ? false : currentLongrunningOperationValue;
            priorLongrunningOperationValue   = priorLongrunningOperationValue == null ? false : priorLongrunningOperationValue;
            if (!currentLongrunningOperationValue.Equals(priorLongrunningOperationValue))
            {
                context.LogBreakingChange(ComparisonMessages.XmsLongRunningOperationChanged);
            }

            CheckParameters(context, priorOperation);

            // Check that all the request body formats that were accepted still are.

            context.PushProperty("consumes");
            foreach (var format in priorOperation.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.PushProperty("produces");
            foreach (var format in Produces)
            {
                if (!priorOperation.Produces.Contains(format))
                {
                    context.LogBreakingChange(ComparisonMessages.ResponseBodyFormatNowSupported, format);
                }
            }
            context.Pop();

            if (Responses != null && priorOperation.Responses != null)
            {
                context.PushProperty("responses");
                foreach (var response in Responses)
                {
                    var oldResponse = priorOperation.FindResponse(response.Key, priorOperation.Responses);

                    context.PushProperty(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.PushProperty(response.Key);
                        context.LogBreakingChange(ComparisonMessages.RemovedResponseCode, response.Key);
                        context.Pop();
                    }
                }
                context.Pop();
            }

            return(context.Messages);
        }
Ejemplo n.º 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 <ServiceDefinition> context,
            ServiceDefinition previousDefinition
            )
        {
            if (context.CurrentRoot != this)
            {
                throw new ArgumentException("context.CurrentRoot != this");
            }
            if (context.PreviousRoot != previousDefinition)
            {
                throw new ArgumentException("context.PreviousRoot != previousDefinition");
            }

            if (previousDefinition == null)
            {
                throw new ArgumentException("Comparing a service definition with something else.");
            }

            base.Compare(context, previousDefinition);

            if (Info?.Version != null &&
                previousDefinition.Info?.Version != null)
            {
                context.PushProperty("info");
                context.PushProperty("version");

                CompareVersions(context, Info.Version, previousDefinition.Info.Version);

                context.Pop();
                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.PushProperty("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.PushProperty("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.PushProperty("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.PushProperty("paths");
            foreach (var path in previousDefinition.Paths.Keys)
            {
                var p = ObjectPath.OpenApiPathName(path);

                context.PushPathProperty(path);

                if (!newPaths.TryGetValue(p, out var operations))
                {
                    // Entrie path was removeed
                    context.LogBreakingChange(ComparisonMessages.RemovedPath, path);
                }
                else
                {
                    // 1. Remove this path from the current list to find the added paths
                    newPaths.Remove(p);
                    var copyOfOperations = operations.ToDictionary(e => e.Key, e => e.Value);

                    // 2. look for operation match inside this path
                    var previousOperations = previousDefinition.Paths[path];
                    foreach (var previousOperation in previousOperations)
                    {
                        if (!operations.TryGetValue(previousOperation.Key, out var newOperation))
                        {
                            // Operation was removed from the path
                            context.LogBreakingChange(ComparisonMessages.RemovedOperation, previousOperation.Value.OperationId);
                        }
                        else
                        {
                            copyOfOperations.Remove(previousOperation.Key);
                        }
                    }

                    // Look for added operations
                    foreach (var copyOfOperation in copyOfOperations)
                    {
                        context.PushProperty(copyOfOperation.Key);
                        context.LogInfo(ComparisonMessages.AddedOperation);
                        context.Pop();
                    }

                    // Compare operations
                    foreach (var operation in operations)
                    {
                        if (previousDefinition.Paths[path].TryGetValue(operation.Key, out var previousOperation))
                        {
                            context.PushProperty(operation.Key);
                            operation.Value.Compare(context, previousOperation);
                            context.Pop();
                        }
                    }
                }
                context.Pop();
            }

            // Check wether any new paths are being added
            foreach (var path in newPaths.Keys)
            {
                context.PushPathProperty(path);
                context.LogInfo(ComparisonMessages.AddedPath);
                context.Pop();
            }


            context.Pop();

            // Check for custom paths : x-ms-paths
            var newCustomPaths = RemovePathVariables(CustomPaths);

            context.PushProperty("x-ms-paths");
            foreach (var path in previousDefinition.CustomPaths.Keys)
            {
                var p = ObjectPath.OpenApiPathName(path);

                context.PushPathProperty(path);

                Dictionary <string, Operation> operations = null;
                if (!newCustomPaths.TryGetValue(p, out operations))
                {
                    context.LogBreakingChange(ComparisonMessages.RemovedPath, path);
                }
                else
                {
                    // 1. Remove this custom path from the current list to find the added paths
                    newCustomPaths.Remove(p);
                    Dictionary <string, Operation> copyOfOperations = operations.ToDictionary(e => e.Key, e => e.Value);

                    // 2. look for operation match inside this path
                    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);
                        }
                    }

                    // Look for added operations
                    foreach (var copyOfOperation in copyOfOperations)
                    {
                        context.PushProperty(copyOfOperation.Key);
                        context.LogInfo(ComparisonMessages.AddedOperation);
                        context.Pop();
                    }

                    // Compare operations
                    foreach (var operation in operations)
                    {
                        Operation previousOperation = null;
                        if (previousDefinition.CustomPaths[path].TryGetValue(operation.Key, out previousOperation))
                        {
                            context.PushProperty(operation.Key);
                            operation.Value.Compare(context, previousOperation);
                            context.Pop();
                        }
                    }
                }
                context.Pop();
            }

            // Check wether any new paths are being added into x-ms-paths
            foreach (var path in newCustomPaths.Keys)
            {
                context.PushPathProperty(path);
                context.LogInfo(ComparisonMessages.AddedPath);
                context.Pop();
            }

            context.Pop();

            ReferenceTrackSchemas(this);
            ReferenceTrackSchemas(previousDefinition);

            context.PushProperty("parameters");
            foreach (var def in previousDefinition.Parameters.Keys)
            {
                SwaggerParameter parameter = null;
                if (!Parameters.TryGetValue(def, out parameter))
                {
                    context.LogBreakingChange(ComparisonMessages.RemovedClientParameter, def);
                }
                else
                {
                    context.PushProperty(def);
                    parameter.Compare(context, previousDefinition.Parameters[def]);
                    context.Pop();
                }
            }
            context.Pop();

            context.PushProperty("responses");
            foreach (var def in previousDefinition.Responses.Keys)
            {
                OperationResponse response = null;
                if (!Responses.TryGetValue(def, out response))
                {
                    context.LogBreakingChange(ComparisonMessages.RemovedDefinition, def);
                }
                else
                {
                    context.PushProperty(def);
                    response.Compare(context, previousDefinition.Responses[def]);
                    context.Pop();
                }
            }
            context.Pop();

            context.PushProperty("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
                {
                    context.PushProperty(def);
                    schema.Compare(context, previousDefinition.Definitions[def]);
                    context.Pop();
                }
            }
            context.Pop();

            context.Pop();

            return(context.Messages);
        }