/// <summary> /// Create a new schema resolver in the context of the given swagger spec /// </summary> /// <param name="modeler">The swagger spec modeler</param> public SchemaResolver(SwaggerModeler modeler) { if (modeler == null) { throw new ArgumentNullException("modeler"); } _serviceDefinition = modeler.ServiceDefinition; }
/// <summary> /// Create a new schema resolver in the context of the given swagger spec /// </summary> /// <param name="modeler">The swagger spec modeler</param> public SchemaResolver(ServiceDefinition definition) { _serviceDefinition = definition; }
public override ServiceClient Build(out IEnumerable<ValidationMessage> messages) { Logger.LogInfo(Resources.ParsingSwagger); if (string.IsNullOrWhiteSpace(Settings.Input)) { throw ErrorManager.CreateError(Resources.InputRequired); } ServiceDefinition = SwaggerParser.Load(Settings.Input, Settings.FileSystem); // Look for semantic errors and warnings in the document. var validator = new RecursiveObjectValidator(PropertyNameResolver.JsonName); messages = validator.GetValidationExceptions(ServiceDefinition).ToList(); Logger.LogInfo(Resources.GeneratingClient); // Update settings UpdateSettings(); InitializeClientModel(); BuildCompositeTypes(); // Build client parameters foreach (var swaggerParameter in ServiceDefinition.Parameters.Values) { var parameter = ((ParameterBuilder)swaggerParameter.GetBuilder(this)).Build(); var clientProperty = new Property(); clientProperty.LoadFrom(parameter); ServiceClient.Properties.Add(clientProperty); } // Build methods foreach (var path in ServiceDefinition.Paths.Concat(ServiceDefinition.CustomPaths)) { foreach (var verb in path.Value.Keys) { var operation = path.Value[verb]; if (string.IsNullOrWhiteSpace(operation.OperationId)) { throw ErrorManager.CreateError( string.Format(CultureInfo.InvariantCulture, Resources.OperationIdMissing, verb, path.Key)); } var methodName = GetMethodName(operation); var methodGroup = GetMethodGroup(operation); if (verb.ToHttpMethod() != HttpMethod.Options) { string url = path.Key; if (url.Contains("?")) { url = url.Substring(0, url.IndexOf('?')); } var method = BuildMethod(verb.ToHttpMethod(), url, methodName, operation); method.Group = methodGroup; ServiceClient.Methods.Add(method); if (method.DefaultResponse.Body is CompositeType) { ServiceClient.ErrorTypes.Add((CompositeType)method.DefaultResponse.Body); } } else { Logger.LogWarning(Resources.OptionsNotSupported); } } } // Set base type foreach (var typeName in GeneratedTypes.Keys) { var objectType = GeneratedTypes[typeName]; if (ExtendedTypes.ContainsKey(typeName)) { objectType.BaseModelType = GeneratedTypes[ExtendedTypes[typeName]]; } ServiceClient.ModelTypes.Add(objectType); } return ServiceClient; }
/// <summary> /// In order to avoid comparing definitions (schemas) that are not used, we go through all references that are /// found in operations, global parameters, and global responses. Definitions that are referenced from other /// definitions are included only by transitive closure. /// </summary> private static void ReferenceTrackSchemas(ServiceDefinition service) { foreach (var schema in service.Definitions.Values) { schema.IsReferenced = false; } foreach (var path in service.Paths.Values) { foreach (var operation in path.Values) { foreach (var parameter in operation.Parameters) { if (parameter.Schema != null && !string.IsNullOrWhiteSpace(parameter.Schema.Reference)) { var schema = FindReferencedSchema(parameter.Schema.Reference, service.Definitions); schema.IsReferenced = true; } } } } foreach (var path in service.CustomPaths.Values) { foreach (var operation in path.Values) { foreach (var parameter in operation.Parameters) { if (parameter.Schema != null && !string.IsNullOrWhiteSpace(parameter.Schema.Reference)) { var schema = FindReferencedSchema(parameter.Schema.Reference, service.Definitions); schema.IsReferenced = true; } } } } foreach (var parameter in service.Parameters.Values) { if (parameter.Schema != null && !string.IsNullOrWhiteSpace(parameter.Schema.Reference)) { var schema = FindReferencedSchema(parameter.Schema.Reference, service.Definitions); schema.IsReferenced = true; } } foreach (var response in service.Responses.Values) { if (response.Schema != null && !string.IsNullOrWhiteSpace(response.Schema.Reference)) { var schema = FindReferencedSchema(response.Schema.Reference, service.Definitions); schema.IsReferenced = true; } } var changed = true; while (changed) { changed = false; foreach (var schema in service.Definitions.Values.Where(d => d.IsReferenced)) { // If schema does not have properties then do not recurse if (schema.Properties == null) { continue; } foreach (var property in schema.Properties.Values) { if (!string.IsNullOrWhiteSpace(property.Reference)) { var s = FindReferencedSchema(property.Reference, service.Definitions); changed = changed || !s.IsReferenced; s.IsReferenced = true; } } } } }
/// <summary> /// In order to avoid comparing definitions (schemas) that are not used, we go through all references that are /// found in operations, global parameters, and global responses. Definitions that are referenced from other /// definitions are included only by transitive closure. /// </summary> private static void ReferenceTrackSchemas(ServiceDefinition service) { foreach (var schema in service.Definitions.Values) { schema.IsReferenced = false; } foreach (var path in service.Paths.Values) { foreach (var operation in path.Values) { foreach (var parameter in operation.Parameters) { if (parameter.Schema != null && !string.IsNullOrWhiteSpace(parameter.Schema.Reference)) { var schema = FindReferencedSchema(parameter.Schema.Reference, service.Definitions); schema.IsReferenced = true; } } } } foreach (var path in service.CustomPaths.Values) { foreach (var operation in path.Values) { foreach (var parameter in operation.Parameters) { if (parameter.Schema != null && !string.IsNullOrWhiteSpace(parameter.Schema.Reference)) { var schema = FindReferencedSchema(parameter.Schema.Reference, service.Definitions); schema.IsReferenced = true; } } } } foreach (var parameter in service.Parameters.Values) { if (parameter.Schema != null && !string.IsNullOrWhiteSpace(parameter.Schema.Reference)) { var schema = FindReferencedSchema(parameter.Schema.Reference, service.Definitions); schema.IsReferenced = true; } } foreach (var response in service.Responses.Values) { if (response.Schema != null && !string.IsNullOrWhiteSpace(response.Schema.Reference)) { var schema = FindReferencedSchema(response.Schema.Reference, service.Definitions); schema.IsReferenced = true; } } var changed = true; while (changed) { changed = false; foreach (var schema in service.Definitions.Values.Where(d => d.IsReferenced)) { // If schema does not have properties then do not recurse if (schema.Properties == null) { continue; } foreach (var property in schema.Properties.Values) { if (!string.IsNullOrWhiteSpace(property.Reference)) { var s = FindReferencedSchema(property.Reference, service.Definitions); changed = changed || !s.IsReferenced; s.IsReferenced = true; } } } } // detect the polymorphic concrecate model. foreach (var schema in service.Definitions.Values.Where(d => !d.IsReferenced)) { if (schema.Extensions != null && schema.Extensions.ContainsKey("x-ms-discriminator-value")) { schema.IsReferenced = true; continue; } if (schema.AllOf == null) { continue; } foreach (var property in schema.AllOf) { if (!string.IsNullOrWhiteSpace(property.Reference)) { if (FindDiscriminator(property.Reference, service.Definitions)) { schema.IsReferenced = true; 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 <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); }
/// <summary> /// In order to avoid comparing definitions (schemas) that are not used, we go through all references that are /// found in operations, global parameters, and global responses. Definitions that are referenced from other /// definitions are included only by transitive closure. /// </summary> private static void ReferenceTrackSchemas(ServiceDefinition service) { foreach (var schema in service.Definitions.Values) { schema.IsReferenced = false; } foreach (var path in service.Paths.Values) { foreach (var operation in path.Values) { foreach (var parameter in operation.Parameters) { if (parameter.Schema != null && !string.IsNullOrWhiteSpace(parameter.Schema.Reference)) { var schema = FindReferencedSchema(parameter.Schema.Reference, service.Definitions); schema.IsReferenced = true; } } } } foreach (var path in service.CustomPaths.Values) { foreach (var operation in path.Values) { foreach (var parameter in operation.Parameters) { if (parameter.Schema != null && !string.IsNullOrWhiteSpace(parameter.Schema.Reference)) { var schema = FindReferencedSchema(parameter.Schema.Reference, service.Definitions); schema.IsReferenced = true; } } } } foreach (var parameter in service.Parameters.Values) { if (parameter.Schema != null && !string.IsNullOrWhiteSpace(parameter.Schema.Reference)) { var schema = FindReferencedSchema(parameter.Schema.Reference, service.Definitions); schema.IsReferenced = true; } } foreach (var response in service.Responses.Values) { if (response.Schema != null && !string.IsNullOrWhiteSpace(response.Schema.Reference)) { var schema = FindReferencedSchema(response.Schema.Reference, service.Definitions); schema.IsReferenced = true; } } var changed = true; while (changed) { changed = false; foreach (var schema in service.Definitions.Values.Where(d => d.IsReferenced)) { foreach (var property in schema.Properties.Values) { if (!string.IsNullOrWhiteSpace(property.Reference)) { var s = FindReferencedSchema(property.Reference, service.Definitions); changed = changed || !s.IsReferenced; s.IsReferenced = true; } } } } }