/// <summary> /// Check that no parameters were removed or reordered, and compare them if it's not the case /// </summary> /// <param name="context">Comaprision Context</param> /// <param name="priorOperation">Operation object of old swagger</param> private void CheckParameters(ComparisonContext <ServiceDefinition> context, Operation priorOperation) { // Check that no parameters were removed or reordered, and compare them if it's not the case. var currentRoot = context.CurrentRoot; var previousRoot = context.PreviousRoot; context.PushProperty("parameters"); var priorOperationParameters = priorOperation.Parameters.Select(param => string.IsNullOrWhiteSpace(param.Reference) ? param : FindReferencedParameter(param.Reference, previousRoot.Parameters) ); foreach (var oldParam in priorOperationParameters) { SwaggerParameter newParam = FindParameter(oldParam.Name, Parameters, currentRoot.Parameters); // we should use PushItemByName instead of PushProperty because Swagger `parameters` is // an array of paremters. context.PushItemByName(oldParam.Name); if (newParam != null) { newParam.Compare(context, oldParam); } else if (oldParam.IsRequired) { // Removed required parameter context.LogBreakingChange(ComparisonMessages.RemovedRequiredParameter, oldParam.Name); } context.Pop(); } // Check that no required parameters were added. var requiredParamters = Parameters.Select(param => string.IsNullOrWhiteSpace(param.Reference) ? param : FindReferencedParameter(param.Reference, currentRoot.Parameters)) .Where(p => p != null && p.IsRequired); foreach (var newParam in requiredParamters) { if (newParam == null) { continue; } SwaggerParameter oldParam = FindParameter(newParam.Name, priorOperation.Parameters, previousRoot.Parameters); if (oldParam == null) { // Did not find required parameter in the old swagger i.e required parameter is added context.PushItemByName(newParam.Name); context.LogBreakingChange(ComparisonMessages.AddingRequiredParameter, newParam.Name); context.Pop(); } } context.Pop(); }
public static StringBuilder OnBuildMethodParameter(Method method, SwaggerParameter currentSwaggerParam, StringBuilder paramNameBuilder) { if (currentSwaggerParam == null) { throw new ArgumentNullException("currentSwaggerParam"); } bool hasCollectionFormat = currentSwaggerParam.CollectionFormat != CollectionFormat.None; if (currentSwaggerParam.Type == DataType.Array && !hasCollectionFormat) { // If the parameter type is array default the collectionFormat to csv currentSwaggerParam.CollectionFormat = CollectionFormat.Csv; } if (hasCollectionFormat) { AddCollectionFormat(currentSwaggerParam, paramNameBuilder); if (currentSwaggerParam.In == ParameterLocation.Path) { if (method == null || method.Url == null) { throw new ArgumentNullException("method"); } method.Url = method.Url.Replace( string.Format(CultureInfo.InvariantCulture, "{0}", currentSwaggerParam.Name), string.Format(CultureInfo.InvariantCulture, "{0}", paramNameBuilder)); } } return paramNameBuilder; }
private int FindParameterIndex(SwaggerParameter parameter, IEnumerable <SwaggerParameter> operationParameters) { for (int i = 0; i < operationParameters.Count(); i++) { if (operationParameters.ElementAt(i).Name == parameter.Name && operationParameters.ElementAt(i).In == parameter.In) { return(i); } } return(-1); }
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.PushProperty(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.PushProperty(newParam.Name); context.LogBreakingChange(ComparisonMessages.AddingRequiredParameter, newParam.Name); context.Pop(); } } }
private static void AddCollectionFormat(SwaggerParameter swaggerParameter, StringBuilder parameterName) { if (swaggerParameter.In == ParameterLocation.FormData) { // http://vstfrd:8080/Azure/RD/_workitems/edit/3172874 throw new NotImplementedException(); } //Debug.Assert(!string.IsNullOrEmpty(swaggerParameter.CollectionFormat)); Debug.Assert(swaggerParameter.CollectionFormat != CollectionFormat.None); parameterName.Append(":"); switch (swaggerParameter.CollectionFormat) { case CollectionFormat.Csv: parameterName.Append("commaSeparated"); break; case CollectionFormat.Pipes: parameterName.Append("pipeSeparated"); break; case CollectionFormat.Ssv: parameterName.Append("spaceSeparated"); break; case CollectionFormat.Tsv: parameterName.Append("tabSeparated"); break; case CollectionFormat.Multi: if (swaggerParameter.In != ParameterLocation.Query) { throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, Resources.MultiCollectionFormatNotSupported, swaggerParameter.Name)); } parameterName.Append("ampersandSeparated"); break; default: throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Resources.InvalidCollectionFormat, swaggerParameter.CollectionFormat, swaggerParameter.Name)); } }
private static SwaggerParameter FindReferencedParameter(string reference, IDictionary <string, SwaggerParameter> parameters) { if (reference != null && reference.StartsWith("#", StringComparison.Ordinal)) { var parts = reference.Split('/'); if (parts.Length == 3 && parts[1].Equals("parameters")) { SwaggerParameter p = null; if (parameters.TryGetValue(parts[2], out p)) { return(p); } } } return(null); }
/// <summary> /// Finds given parameter in the list of operation parameters or global parameters , /// </summary> /// <param name="parameter">the parameter to search</param> /// <param name="operationParameters">list of operation parameters to search</param> /// <param name="clientParameters">Dictionary of global paramters to search</param> /// <returns>Swagger Parameter if found; otherwise null</returns> private SwaggerParameter FindParameterEx(SwaggerParameter parameter, IEnumerable <SwaggerParameter> operationParameters, IDictionary <string, SwaggerParameter> clientParameters) { if (Parameters != null) { ///first try to find the param has same 'name' and 'in' foreach (var param in operationParameters) { if (parameter.Name.Equals(param.Name) && parameter.In.Equals(param.In)) { return(param); } var pRef = FindReferencedParameter(param.Reference, clientParameters); if (pRef != null && parameter.Name.Equals(pRef.Name) && parameter.In.Equals(pRef.In)) { return(pRef); } } } /// then try to find the parameter has same 'name' return(FindParameter(parameter.Name, operationParameters, clientParameters)); }
/// <summary> /// Check that no parameters were removed or reordered, and compare them if it's not the case /// </summary> /// <param name="context">Comaprision Context</param> /// <param name="priorOperation">Operation object of old swagger</param> private void CheckParameters(ComparisonContext <ServiceDefinition> context, Operation priorOperation) { // Check that no parameters were removed or reordered, and compare them if it's not the case. var currentRoot = context.CurrentRoot; var previousRoot = context.PreviousRoot; context.PushProperty("parameters"); var priorOperationParameters = priorOperation.Parameters.Select(param => string.IsNullOrWhiteSpace(param.Reference) ? param : FindReferencedParameter(param.Reference, previousRoot.Parameters) ); var currentOperationParameters = Parameters.Select(param => string.IsNullOrWhiteSpace(param.Reference) ? param : FindReferencedParameter(param.Reference, currentRoot.Parameters)); for (int i = 0; i < currentOperationParameters.Count(); i++) { var curOriginalParameter = Parameters.ElementAt(i); var curParameter = currentOperationParameters.ElementAt(i); curParameter.Extensions.TryGetValue("x-ms-long-running-operation", out var curParameterLocation); if ( !string.IsNullOrWhiteSpace(curOriginalParameter.Reference) && (curParameterLocation == null || !curParameterLocation.Equals("method")) ) { continue; } var priorIndex = FindParameterIndex(curParameter, priorOperationParameters); if (priorIndex != -1 && priorIndex != i) { context.LogBreakingChange(ComparisonMessages.ChangedParameterOrder, curParameter.Name); } } foreach (var oldParam in priorOperationParameters) { SwaggerParameter newParam = FindParameterEx(oldParam, Parameters, currentRoot.Parameters); // we should use PushItemByName instead of PushProperty because Swagger `parameters` is // an array of paremters. context.PushItemByName(oldParam.Name); if (newParam != null) { newParam.Compare(context, oldParam); } else if (oldParam.IsRequired) { // Removed required parameter context.LogBreakingChange(ComparisonMessages.RemovedRequiredParameter, oldParam.Name); } else { // Removed optional parameter context.LogBreakingChange(ComparisonMessages.RemovedOptionalParameter, oldParam.Name); } context.Pop(); } // Check that no required or optional parameters were added. var allParamters = Parameters.Select(param => string.IsNullOrWhiteSpace(param.Reference) ? param : FindReferencedParameter(param.Reference, currentRoot.Parameters)) .Where(p => p != null); foreach (var newParam in allParamters) { if (newParam == null) { continue; } SwaggerParameter oldParam = FindParameterEx(newParam, priorOperation.Parameters, previousRoot.Parameters); if (oldParam == null) { // Did not find required parameter in the old swagger i.e required parameter is added context.PushItemByName(newParam.Name); if (newParam.IsRequired) { context.LogBreakingChange(ComparisonMessages.AddingRequiredParameter, newParam.Name); } else { context.LogBreakingChange(ComparisonMessages.AddingOptionalParameter, newParam.Name); } context.Pop(); } } context.Pop(); }
public SwaggerParameter Unwrap(SwaggerParameter swaggerParameter) { if (swaggerParameter == null) { throw new ArgumentNullException("swaggerParameter"); } // If referencing global parameters serializationProperty if (swaggerParameter.Reference != null) { string referenceKey = swaggerParameter.Reference.StripParameterPath(); if (!ServiceDefinition.Parameters.ContainsKey(referenceKey)) { throw new ArgumentException( string.Format(CultureInfo.InvariantCulture, Resources.DefinitionDoesNotExist, referenceKey)); } swaggerParameter = ServiceDefinition.Parameters[referenceKey]; } // Unwrap the schema if in "body" if (swaggerParameter.Schema != null && swaggerParameter.In == ParameterLocation.Body) { swaggerParameter.Schema = Resolver.Unwrap(swaggerParameter.Schema); } return swaggerParameter; }
/// <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); }
/// <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); }
public ParameterBuilder(SwaggerParameter swaggerParameter, SwaggerModeler modeler) : base(swaggerParameter, modeler) { _swaggerParameter = swaggerParameter; }