diff --git a/.editorconfig b/.editorconfig index da826a9..131b58e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -21,8 +21,9 @@ resharper_string_literal_typo_highlighting = none insert_final_newline = false [*.cs] -indent_size = 4 +indent_size = tab indent_style = tab +tab_width = 4 csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true @@ -75,22 +76,30 @@ csharp_style_expression_bodied_local_functions = true : suggestion csharp_style_expression_bodied_methods = true : suggestion csharp_style_expression_bodied_operators = true : suggestion csharp_style_expression_bodied_properties = true : suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true : suggestion csharp_style_inlined_variable_declaration = true : suggestion csharp_style_namespace_declarations = file_scoped : suggestion -csharp_style_pattern_local_over_anonymous_function = true : suggestion csharp_style_pattern_matching_over_as_with_null_check = true : suggestion csharp_style_pattern_matching_over_is_with_cast_check = true : suggestion +csharp_style_prefer_extended_property_pattern = true : suggestion csharp_style_prefer_index_operator = true : suggestion +csharp_style_prefer_local_over_anonymous_function = true : suggestion +csharp_style_prefer_not_pattern = true : suggestion +csharp_style_prefer_null_check_over_type_check = true : suggestion +csharp_style_prefer_pattern_matching = true : suggestion csharp_style_prefer_range_operator = true : suggestion csharp_style_prefer_switch_expression = true : suggestion +csharp_style_prefer_tuple_swap = true : warning csharp_style_throw_expression = true : suggestion -csharp_style_unused_value_assignment_preference = discard_variable : suggestion +csharp_style_unused_value_assignment_preference = discard_variable : warning csharp_style_unused_value_expression_statement_preference = discard_variable : none csharp_style_var_elsewhere = true : suggestion csharp_style_var_for_built_in_types = true : suggestion csharp_style_var_when_type_is_apparent = true : suggestion csharp_using_directive_placement = outside_namespace : warning +dotnet_analyzer_diagnostic.severity = warning dotnet_code_quality_unused_parameters = all : suggestion +dotnet_diagnostic.CA1014.severity = none dotnet_diagnostic.CA1030.severity = none dotnet_diagnostic.CA1031.severity = suggestion dotnet_diagnostic.CA1032.severity = suggestion @@ -101,6 +110,8 @@ dotnet_diagnostic.CA1062.severity = suggestion dotnet_diagnostic.CA1063.severity = none dotnet_diagnostic.CA1303.severity = none dotnet_diagnostic.CA1308.severity = suggestion +dotnet_diagnostic.CA1309.severity = suggestion +dotnet_diagnostic.CA1508.severity = suggestion dotnet_diagnostic.CA1707.severity = none dotnet_diagnostic.CA1716.severity = none dotnet_diagnostic.CA1720.severity = suggestion @@ -110,10 +121,89 @@ dotnet_diagnostic.CA1816.severity = none dotnet_diagnostic.CA1819.severity = suggestion dotnet_diagnostic.CA1822.severity = suggestion dotnet_diagnostic.CA1826.severity = suggestion +dotnet_diagnostic.CA1848.severity = suggestion dotnet_diagnostic.CA2000.severity = none dotnet_diagnostic.CA2227.severity = none dotnet_diagnostic.CA2234.severity = none dotnet_diagnostic.CA2237.severity = none +dotnet_diagnostic.CA2254.severity = none +dotnet_diagnostic.CA5351.severity = none +dotnet_diagnostic.IDE0001.severity = warning +dotnet_diagnostic.IDE0002.severity = warning +dotnet_diagnostic.IDE0003.severity = warning +dotnet_diagnostic.IDE0004.severity = warning +dotnet_diagnostic.IDE0005.severity = warning +dotnet_diagnostic.IDE0007.severity = suggestion +dotnet_diagnostic.IDE0008.severity = suggestion +dotnet_diagnostic.IDE0009.severity = warning +dotnet_diagnostic.IDE0010.severity = suggestion +dotnet_diagnostic.IDE0011.severity = suggestion +dotnet_diagnostic.IDE0016.severity = suggestion +dotnet_diagnostic.IDE0017.severity = suggestion +dotnet_diagnostic.IDE0018.severity = suggestion +dotnet_diagnostic.IDE0019.severity = suggestion +dotnet_diagnostic.IDE0020.severity = suggestion +dotnet_diagnostic.IDE0021.severity = suggestion +dotnet_diagnostic.IDE0022.severity = suggestion +dotnet_diagnostic.IDE0023.severity = suggestion +dotnet_diagnostic.IDE0024.severity = suggestion +dotnet_diagnostic.IDE0025.severity = suggestion +dotnet_diagnostic.IDE0026.severity = suggestion +dotnet_diagnostic.IDE0027.severity = suggestion +dotnet_diagnostic.IDE0028.severity = suggestion +dotnet_diagnostic.IDE0029.severity = warning +dotnet_diagnostic.IDE0030.severity = warning +dotnet_diagnostic.IDE0031.severity = suggestion +dotnet_diagnostic.IDE0032.severity = suggestion +dotnet_diagnostic.IDE0033.severity = warning +dotnet_diagnostic.IDE0034.severity = suggestion +dotnet_diagnostic.IDE0035.severity = warning +dotnet_diagnostic.IDE0036.severity = warning +dotnet_diagnostic.IDE0037.severity = suggestion +dotnet_diagnostic.IDE0038.severity = suggestion +dotnet_diagnostic.IDE0039.severity = suggestion +dotnet_diagnostic.IDE0040.severity = warning +dotnet_diagnostic.IDE0041.severity = suggestion +dotnet_diagnostic.IDE0042.severity = suggestion +dotnet_diagnostic.IDE0044.severity = warning +dotnet_diagnostic.IDE0045.severity = suggestion +dotnet_diagnostic.IDE0046.severity = none +dotnet_diagnostic.IDE0047.severity = suggestion +dotnet_diagnostic.IDE0048.severity = suggestion +dotnet_diagnostic.IDE0049.severity = warning +dotnet_diagnostic.IDE0051.severity = warning +dotnet_diagnostic.IDE0052.severity = warning +dotnet_diagnostic.IDE0053.severity = suggestion +dotnet_diagnostic.IDE0054.severity = suggestion +dotnet_diagnostic.IDE0056.severity = suggestion +dotnet_diagnostic.IDE0057.severity = suggestion +dotnet_diagnostic.IDE0058.severity = none +dotnet_diagnostic.IDE0059.severity = warning +dotnet_diagnostic.IDE0060.severity = suggestion +dotnet_diagnostic.IDE0061.severity = suggestion +dotnet_diagnostic.IDE0062.severity = suggestion +dotnet_diagnostic.IDE0063.severity = suggestion +dotnet_diagnostic.IDE0065.severity = warning +dotnet_diagnostic.IDE0066.severity = suggestion +dotnet_diagnostic.IDE0070.severity = suggestion +dotnet_diagnostic.IDE0071.severity = suggestion +dotnet_diagnostic.IDE0072.severity = suggestion +dotnet_diagnostic.IDE0074.severity = suggestion +dotnet_diagnostic.IDE0075.severity = warning +dotnet_diagnostic.IDE0078.severity = suggestion +dotnet_diagnostic.IDE0080.severity = suggestion +dotnet_diagnostic.IDE0082.severity = warning +dotnet_diagnostic.IDE0083.severity = suggestion +dotnet_diagnostic.IDE0090.severity = suggestion +dotnet_diagnostic.IDE0100.severity = warning +dotnet_diagnostic.IDE0110.severity = warning +dotnet_diagnostic.IDE0130.severity = warning +dotnet_diagnostic.IDE0150.severity = suggestion +dotnet_diagnostic.IDE0160.severity = suggestion +dotnet_diagnostic.IDE0161.severity = suggestion +dotnet_diagnostic.IDE0170.severity = suggestion +dotnet_diagnostic.IDE0180.severity = warning +dotnet_diagnostic.IDE1005.severity = suggestion dotnet_diagnostic.SA0001.severity = none dotnet_diagnostic.SA1003.severity = none dotnet_diagnostic.SA1008.severity = none @@ -161,6 +251,8 @@ dotnet_diagnostic.SA1633.severity = none dotnet_diagnostic.SA1642.severity = none dotnet_diagnostic.SA1643.severity = none dotnet_diagnostic.SX1101.severity = warning +dotnet_diagnostic.SYSLIB1045.severity = suggestion +dotnet_diagnostic.SYSLIB1054.severity = none dotnet_naming_rule.local_functions_rule.severity = warning dotnet_naming_rule.local_functions_rule.style = upper_camel_case_style dotnet_naming_rule.local_functions_rule.symbols = local_functions_symbols @@ -201,6 +293,7 @@ dotnet_sort_system_directives_first = true dotnet_style_coalesce_expression = true : warning dotnet_style_collection_initializer = true : suggestion dotnet_style_explicit_tuple_names = true : warning +dotnet_style_namespace_match_folder = true : warning dotnet_style_null_propagation = true : suggestion dotnet_style_object_initializer = true : suggestion dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity : none @@ -216,6 +309,8 @@ dotnet_style_prefer_conditional_expression_over_return = true : none dotnet_style_prefer_inferred_anonymous_type_member_names = true : suggestion dotnet_style_prefer_inferred_tuple_names = true : suggestion dotnet_style_prefer_is_null_check_over_reference_equality_method = true : suggestion +dotnet_style_prefer_simplified_boolean_expressions = true : warning +dotnet_style_prefer_simplified_interpolation = true : suggestion dotnet_style_qualification_for_event = false : warning dotnet_style_qualification_for_field = false : warning dotnet_style_qualification_for_method = false : warning diff --git a/Directory.Build.props b/Directory.Build.props index f157125..65113ae 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ enable true en-US - $(NoWarn);1591;1998;NU5105;CA1014;CA1508;CA1852 + $(NoWarn);1591;1998;NU5105 embedded FacilityApi FacilitySwagger @@ -21,6 +21,8 @@ true true AllEnabledByDefault + true + true false false @@ -31,13 +33,13 @@ - 2.9.0 + 2.10.1 - + - + diff --git a/README.md b/README.md index d4eaac9..2bbc074 100644 --- a/README.md +++ b/README.md @@ -9,4 +9,4 @@ Name | Description | NuGet Facility.Definition.Swagger | Converts OpenAPI 2.0 (Swagger) to/from a Facility Service Definition. | [![NuGet](https://img.shields.io/nuget/v/Facility.Definition.Swagger.svg)](https://www.nuget.org/packages/Facility.Definition.Swagger) fsdgenswagger | A tool that converts OpenAPI 2.0 (Swagger) to/from a Facility Service Definition. | [![NuGet](https://img.shields.io/nuget/v/fsdgenswagger.svg)](https://www.nuget.org/packages/fsdgenswagger) -[Documentation](https://facilityapi.github.io/) | [Release Notes](ReleaseNotes.md) | [Contributing](CONTRIBUTING.md) +[Documentation](https://facilityapi.github.io/) | [Release Notes](https://github.com/FacilityApi/FacilitySwagger/blob/master/ReleaseNotes.md) | [Contributing](https://github.com/FacilityApi/FacilitySwagger/blob/master/CONTRIBUTING.md) diff --git a/dotnet-tools.json b/dotnet-tools.json deleted file mode 100644 index 155104a..0000000 --- a/dotnet-tools.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "dotnet-format": { - "version": "5.1.250801", - "commands": [ - "dotnet-format" - ] - }, - "jetbrains.resharper.globaltools": { - "version": "2022.3.1", - "commands": [ - "jb" - ] - } - } -} \ No newline at end of file diff --git a/src/Facility.Definition.Swagger/Facility.Definition.Swagger.csproj b/src/Facility.Definition.Swagger/Facility.Definition.Swagger.csproj index 4fb50dd..fd257ed 100644 --- a/src/Facility.Definition.Swagger/Facility.Definition.Swagger.csproj +++ b/src/Facility.Definition.Swagger/Facility.Definition.Swagger.csproj @@ -5,7 +5,7 @@ Used to interpret Swagger (OpenAPI) 2.0 definitions. Facility FSD Swagger OpenAPI Definition true - true + README.md @@ -13,4 +13,9 @@ + + + + + diff --git a/src/Facility.Definition.Swagger/ISwaggerSchema.cs b/src/Facility.Definition.Swagger/ISwaggerSchema.cs index 39bcbad..58a50f9 100644 --- a/src/Facility.Definition.Swagger/ISwaggerSchema.cs +++ b/src/Facility.Definition.Swagger/ISwaggerSchema.cs @@ -1,50 +1,46 @@ -using System.Collections.Generic; using Newtonsoft.Json.Linq; -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public interface ISwaggerSchema { - public interface ISwaggerSchema - { - string? Ref { get; set; } // parameters, schema + string? Ref { get; set; } // parameters, schema - string? Description { get; set; } // parameters, headers, schema + string? Description { get; set; } // parameters, headers, schema - string? Type { get; set; } // parameters (non-body), headers, schema + string? Type { get; set; } // parameters (non-body), headers, schema - string? Format { get; set; } // parameters (non-body), headers, schema + string? Format { get; set; } // parameters (non-body), headers, schema - SwaggerSchema? Items { get; set; } // parameters (non-body), headers, schema + SwaggerSchema? Items { get; set; } // parameters (non-body), headers, schema - JToken? Default { get; set; } // parameters (non-body), headers, schema + JToken? Default { get; set; } // parameters (non-body), headers, schema - double? Maximum { get; set; } // parameters (non-body), headers, schema + double? Maximum { get; set; } // parameters (non-body), headers, schema - bool? ExclusiveMaximum { get; set; } // parameters (non-body), headers, schema + bool? ExclusiveMaximum { get; set; } // parameters (non-body), headers, schema - double? Minimum { get; set; } // parameters (non-body), headers, schema + double? Minimum { get; set; } // parameters (non-body), headers, schema - bool? ExclusiveMinimum { get; set; } // parameters (non-body), headers, schema + bool? ExclusiveMinimum { get; set; } // parameters (non-body), headers, schema - int? MaxLength { get; set; } // parameters (non-body), headers, schema + int? MaxLength { get; set; } // parameters (non-body), headers, schema - int? MinLength { get; set; } // parameters (non-body), headers, schema + int? MinLength { get; set; } // parameters (non-body), headers, schema - string? Pattern { get; set; } // parameters (non-body), headers, schema + string? Pattern { get; set; } // parameters (non-body), headers, schema - int? MaxItems { get; set; } // parameters (non-body), headers, schema + int? MaxItems { get; set; } // parameters (non-body), headers, schema - int? MinItems { get; set; } // parameters (non-body), headers, schema + int? MinItems { get; set; } // parameters (non-body), headers, schema - bool? UniqueItems { get; set; } // parameters (non-body), headers, schema + bool? UniqueItems { get; set; } // parameters (non-body), headers, schema - IList? Enum { get; set; } // parameters (non-body), headers, schema + IList? Enum { get; set; } // parameters (non-body), headers, schema - double? MultipleOf { get; set; } // parameters (non-body), headers, schema + double? MultipleOf { get; set; } // parameters (non-body), headers, schema - string? Identifier { get; set; } // parameters, headers, schema + string? Identifier { get; set; } // parameters, headers, schema - bool? Obsolete { get; set; } // parameters, headers, schema - } + bool? Obsolete { get; set; } // parameters, headers, schema } diff --git a/src/Facility.Definition.Swagger/SwaggerContact.cs b/src/Facility.Definition.Swagger/SwaggerContact.cs index 67bd803..e00441c 100644 --- a/src/Facility.Definition.Swagger/SwaggerContact.cs +++ b/src/Facility.Definition.Swagger/SwaggerContact.cs @@ -1,13 +1,10 @@ -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public class SwaggerContact { - public class SwaggerContact - { - public string? Name { get; set; } + public string? Name { get; set; } - public string? Url { get; set; } + public string? Url { get; set; } - public string? Email { get; set; } - } + public string? Email { get; set; } } diff --git a/src/Facility.Definition.Swagger/SwaggerConversion.cs b/src/Facility.Definition.Swagger/SwaggerConversion.cs index 010807a..a0e08f2 100644 --- a/src/Facility.Definition.Swagger/SwaggerConversion.cs +++ b/src/Facility.Definition.Swagger/SwaggerConversion.cs @@ -1,523 +1,519 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Net; using System.Text.RegularExpressions; using Facility.Definition.CodeGen; -namespace Facility.Definition.Swagger +namespace Facility.Definition.Swagger; + +internal sealed class SwaggerConversion { - internal sealed class SwaggerConversion + public static SwaggerConversion Create(SwaggerService swaggerService, string? serviceName, SwaggerParserContext context) { - public static SwaggerConversion Create(SwaggerService swaggerService, string? serviceName, SwaggerParserContext context) - { - var conversion = new SwaggerConversion(swaggerService, serviceName); - conversion.Convert(context); - return conversion; - } + var conversion = new SwaggerConversion(swaggerService, serviceName); + conversion.Convert(context); + return conversion; + } + + public ServiceInfo? Service { get; private set; } - public ServiceInfo? Service { get; private set; } + public IReadOnlyList Errors => m_errors; - public IReadOnlyList Errors => m_errors; + private SwaggerConversion(SwaggerService swaggerService, string? serviceName) + { + m_swaggerService = swaggerService; + m_serviceName = serviceName; + m_errors = new List(); + } - private SwaggerConversion(SwaggerService swaggerService, string? serviceName) + private void Convert(SwaggerParserContext context) + { + if (m_swaggerService.Swagger == null) + m_errors.Add(context.CreateError("swagger field is missing.")); + else if (m_swaggerService.Swagger != SwaggerUtility.SwaggerVersion) + m_errors.Add(context.CreateError($"swagger should be '{SwaggerUtility.SwaggerVersion}'.", "swagger")); + + if (m_swaggerService.Info == null) + m_errors.Add(context.CreateError("info is missing.")); + + var name = m_serviceName; + if (name != null && !ServiceDefinitionUtility.IsValidName(name)) + m_errors.Add(context.CreateError("ServiceName generator option is not a valid service name.")); + if (name == null) + name = m_swaggerService.Info?.Identifier; + if (name != null && !ServiceDefinitionUtility.IsValidName(name)) + m_errors.Add(context.CreateError("info/x-identifier is not a valid service name.", "info/x-identifier")); + if (name == null && m_swaggerService.Info?.Title is string title) + name = CodeGenUtility.ToPascalCase(title); + if (name == null) + m_errors.Add(context.CreateError("info/title is missing.", "info")); + if (name != null && !ServiceDefinitionUtility.IsValidName(name)) + m_errors.Add(context.CreateError("info/title is not a valid service name.", "info/title")); + + var attributes = new List(); + + var version = m_swaggerService.Info?.Version; + if (!string.IsNullOrWhiteSpace(version)) { - m_swaggerService = swaggerService; - m_serviceName = serviceName; - m_errors = new List(); + attributes.Add(new ServiceAttributeInfo("info", + new[] { new ServiceAttributeParameterInfo("version", version!, context.CreatePart("info/version")!) }, + context.CreatePart("info")!)); } - private void Convert(SwaggerParserContext context) + var scheme = GetBestScheme(m_swaggerService.Schemes); + var host = m_swaggerService.Host; + var basePath = m_swaggerService.BasePath ?? ""; + if (!string.IsNullOrWhiteSpace(host) && !string.IsNullOrWhiteSpace(scheme)) { - if (m_swaggerService.Swagger == null) - m_errors.Add(context.CreateError("swagger field is missing.")); - else if (m_swaggerService.Swagger != SwaggerUtility.SwaggerVersion) - m_errors.Add(context.CreateError($"swagger should be '{SwaggerUtility.SwaggerVersion}'.", "swagger")); - - if (m_swaggerService.Info == null) - m_errors.Add(context.CreateError("info is missing.")); - - var name = m_serviceName; - if (name != null && !ServiceDefinitionUtility.IsValidName(name)) - m_errors.Add(context.CreateError("ServiceName generator option is not a valid service name.")); - if (name == null) - name = m_swaggerService.Info?.Identifier; - if (name != null && !ServiceDefinitionUtility.IsValidName(name)) - m_errors.Add(context.CreateError("info/x-identifier is not a valid service name.", "info/x-identifier")); - if (name == null && m_swaggerService.Info?.Title is string title) - name = CodeGenUtility.ToPascalCase(title); - if (name == null) - m_errors.Add(context.CreateError("info/title is missing.", "info")); - if (name != null && !ServiceDefinitionUtility.IsValidName(name)) - m_errors.Add(context.CreateError("info/title is not a valid service name.", "info/title")); + string url = new UriBuilder(scheme!, host!) { Path = basePath }.Uri.AbsoluteUri; + attributes.Add(new ServiceAttributeInfo("http", + new[] { new ServiceAttributeParameterInfo("url", url) })); + } - var attributes = new List(); + var position = context.CreatePosition(); - var version = m_swaggerService.Info?.Version; - if (!string.IsNullOrWhiteSpace(version)) - { - attributes.Add(new ServiceAttributeInfo("info", - new[] { new ServiceAttributeParameterInfo("version", version!, context.CreatePart("info/version")!) }, - context.CreatePart("info")!)); - } + var members = new List(); - var scheme = GetBestScheme(m_swaggerService.Schemes); - var host = m_swaggerService.Host; - var basePath = m_swaggerService.BasePath ?? ""; - if (!string.IsNullOrWhiteSpace(host) && !string.IsNullOrWhiteSpace(scheme)) + foreach (var swaggerPath in m_swaggerService.Paths.EmptyIfNull()) + { + var swaggerOperations = swaggerPath.Value; + var operationsContext = context.CreateContext("paths/swaggerPath"); + ResolveOperations(ref swaggerOperations, ref operationsContext); + AddServiceMethod(members, "GET", swaggerPath.Key, swaggerOperations.Get, swaggerOperations.Parameters, operationsContext.CreateContext("get")); + AddServiceMethod(members, "POST", swaggerPath.Key, swaggerOperations.Post, swaggerOperations.Parameters, operationsContext.CreateContext("post")); + AddServiceMethod(members, "PUT", swaggerPath.Key, swaggerOperations.Put, swaggerOperations.Parameters, operationsContext.CreateContext("put")); + AddServiceMethod(members, "DELETE", swaggerPath.Key, swaggerOperations.Delete, swaggerOperations.Parameters, operationsContext.CreateContext("delete")); + AddServiceMethod(members, "OPTIONS", swaggerPath.Key, swaggerOperations.Options, swaggerOperations.Parameters, operationsContext.CreateContext("options")); + AddServiceMethod(members, "HEAD", swaggerPath.Key, swaggerOperations.Head, swaggerOperations.Parameters, operationsContext.CreateContext("head")); + AddServiceMethod(members, "PATCH", swaggerPath.Key, swaggerOperations.Patch, swaggerOperations.Parameters, operationsContext.CreateContext("patch")); + } + + foreach (var swaggerDefinition in m_swaggerService.Definitions.EmptyIfNull()) + { + if ((swaggerDefinition.Value.Type ?? SwaggerSchemaType.Object) == SwaggerSchemaType.Object && + !members.OfType().Any(x => swaggerDefinition.Key.Equals(x.Name + "Request", StringComparison.OrdinalIgnoreCase)) && + !members.OfType().Any(x => swaggerDefinition.Key.Equals(x.Name + "Response", StringComparison.OrdinalIgnoreCase)) && + !IsFacilityError(swaggerDefinition) && + TryGetFacilityResultOfType(swaggerDefinition, position) == null) { - string url = new UriBuilder(scheme!, host!) { Path = basePath }.Uri.AbsoluteUri; - attributes.Add(new ServiceAttributeInfo("http", - new[] { new ServiceAttributeParameterInfo("url", url) })); + AddServiceDto(members, swaggerDefinition.Key, swaggerDefinition.Value, context.CreatePart("definitions/" + swaggerDefinition.Key)!); } + } - var position = context.CreatePosition(); - - var members = new List(); + Service = new ServiceInfo(name: name ?? "Api", members: members, attributes: attributes, + summary: PrepareSummary(m_swaggerService.Info?.Title), + remarks: SplitRemarks(m_swaggerService.Info?.Description)); + m_errors.AddRange(Service.GetValidationErrors()); + } - foreach (var swaggerPath in m_swaggerService.Paths.EmptyIfNull()) - { - var swaggerOperations = swaggerPath.Value; - var operationsContext = context.CreateContext("paths/swaggerPath"); - ResolveOperations(ref swaggerOperations, ref operationsContext); - AddServiceMethod(members, "GET", swaggerPath.Key, swaggerOperations.Get, swaggerOperations.Parameters, operationsContext.CreateContext("get")); - AddServiceMethod(members, "POST", swaggerPath.Key, swaggerOperations.Post, swaggerOperations.Parameters, operationsContext.CreateContext("post")); - AddServiceMethod(members, "PUT", swaggerPath.Key, swaggerOperations.Put, swaggerOperations.Parameters, operationsContext.CreateContext("put")); - AddServiceMethod(members, "DELETE", swaggerPath.Key, swaggerOperations.Delete, swaggerOperations.Parameters, operationsContext.CreateContext("delete")); - AddServiceMethod(members, "OPTIONS", swaggerPath.Key, swaggerOperations.Options, swaggerOperations.Parameters, operationsContext.CreateContext("options")); - AddServiceMethod(members, "HEAD", swaggerPath.Key, swaggerOperations.Head, swaggerOperations.Parameters, operationsContext.CreateContext("head")); - AddServiceMethod(members, "PATCH", swaggerPath.Key, swaggerOperations.Patch, swaggerOperations.Parameters, operationsContext.CreateContext("patch")); - } + private static string? GetBestScheme(IList? schemes) + { + return schemes?.FirstOrDefault(x => x == "https") ?? schemes?.FirstOrDefault(x => x == "http") ?? schemes?.FirstOrDefault(); + } - foreach (var swaggerDefinition in m_swaggerService.Definitions.EmptyIfNull()) - { - if ((swaggerDefinition.Value.Type ?? SwaggerSchemaType.Object) == SwaggerSchemaType.Object && - !members.OfType().Any(x => swaggerDefinition.Key.Equals(x.Name + "Request", StringComparison.OrdinalIgnoreCase)) && - !members.OfType().Any(x => swaggerDefinition.Key.Equals(x.Name + "Response", StringComparison.OrdinalIgnoreCase)) && - !IsFacilityError(swaggerDefinition) && - TryGetFacilityResultOfType(swaggerDefinition, position) == null) - { - AddServiceDto(members, swaggerDefinition.Key, swaggerDefinition.Value, context.CreatePart("definitions/" + swaggerDefinition.Key)!); - } - } + private void AddServiceDto(List members, string name, SwaggerSchema schema, ServicePart part) + { + var attributes = new List(); - Service = new ServiceInfo(name: name ?? "Api", members: members, attributes: attributes, - summary: PrepareSummary(m_swaggerService.Info?.Title), - remarks: SplitRemarks(m_swaggerService.Info?.Description)); - m_errors.AddRange(Service.GetValidationErrors()); - } + if (schema.Obsolete.GetValueOrDefault()) + attributes.Add(new ServiceAttributeInfo("obsolete")); - private static string? GetBestScheme(IList? schemes) - { - return schemes?.FirstOrDefault(x => x == "https") ?? schemes?.FirstOrDefault(x => x == "http") ?? schemes?.FirstOrDefault(); - } + var fields = new List(); - private void AddServiceDto(List members, string name, SwaggerSchema schema, ServicePart part) + foreach (var property in schema.Properties.EmptyIfNull()) { - var attributes = new List(); - - if (schema.Obsolete.GetValueOrDefault()) - attributes.Add(new ServiceAttributeInfo("obsolete")); + var fieldAttributes = new List(); - var fields = new List(); + if (property.Value.Obsolete.GetValueOrDefault()) + fieldAttributes.Add(new ServiceAttributeInfo("obsolete")); + if (schema.Required?.Contains(property.Key) == true) + fieldAttributes.Add(new ServiceAttributeInfo("required")); - foreach (var property in schema.Properties.EmptyIfNull()) + var typeName = TryGetFacilityTypeName(property.Value, part.Position); + if (typeName != null) { - var fieldAttributes = new List(); - - if (property.Value.Obsolete.GetValueOrDefault()) - fieldAttributes.Add(new ServiceAttributeInfo("obsolete")); - if (schema.Required?.Contains(property.Key) == true) - fieldAttributes.Add(new ServiceAttributeInfo("required")); - - var typeName = TryGetFacilityTypeName(property.Value, part.Position); - if (typeName != null) - { - fields.Add(new ServiceFieldInfo( - property.Key, - typeName: typeName, - attributes: fieldAttributes, - summary: PrepareSummary(property.Value.Description), - parts: part)); - } + fields.Add(new ServiceFieldInfo( + property.Key, + typeName: typeName, + attributes: fieldAttributes, + summary: PrepareSummary(property.Value.Description), + parts: part)); } - - members.Add(new ServiceDtoInfo( - name: name, - fields: fields, - attributes: attributes, - summary: PrepareSummary(schema.Description), - remarks: SplitRemarks(schema.Remarks), - parts: part)); } - private void AddServiceMethod(IList members, string method, string path, SwaggerOperation? swaggerOperation, IList? swaggerOperationsParameters, SwaggerParserContext context) - { - if (swaggerOperation == null) - return; + members.Add(new ServiceDtoInfo( + name: name, + fields: fields, + attributes: attributes, + summary: PrepareSummary(schema.Description), + remarks: SplitRemarks(schema.Remarks), + parts: part)); + } - var part = context.CreatePart(); + private void AddServiceMethod(IList members, string method, string path, SwaggerOperation? swaggerOperation, IList? swaggerOperationsParameters, SwaggerParserContext context) + { + if (swaggerOperation == null) + return; - path = s_pathParameter.Replace(path, match => - { - string paramName = match.ToString().Substring(1, match.Length - 2); - if (!ServiceDefinitionUtility.IsValidName(paramName)) - paramName = CodeGenUtility.ToCamelCase(paramName); - return $"{{{paramName}}}"; - }); + var part = context.CreatePart(); - var name = swaggerOperation.OperationId == null ? null : CodeGenUtility.ToCamelCase(swaggerOperation.OperationId); - if (!ServiceDefinitionUtility.IsValidName(name)) - name = CodeGenUtility.ToCamelCase($"{method} {path}"); + path = s_pathParameter.Replace(path, match => + { + string paramName = match.ToString().Substring(1, match.Length - 2); + if (!ServiceDefinitionUtility.IsValidName(paramName)) + paramName = CodeGenUtility.ToCamelCase(paramName); + return $"{{{paramName}}}"; + }); - var httpAttributeValues = new List - { - new ServiceAttributeParameterInfo("method", method), - new ServiceAttributeParameterInfo("path", path), - }; - - var requestFields = new List(); - foreach (var swaggerParameter in swaggerOperationsParameters.EmptyIfNull().Concat(swaggerOperation.Parameters.EmptyIfNull())) - AddRequestFields(requestFields, ResolveParameter(swaggerParameter, part?.Position), name!, method, part); - - var responseFields = new List(); - var swaggerResponsePairs = swaggerOperation.Responses.EmptyIfNull() - .Where(x => x.Key[0] == '2' || x.Key[0] == '3' || !string.IsNullOrEmpty(x.Value.Identifier)).ToList(); - foreach (var swaggerResponsePair in swaggerResponsePairs) - { - AddResponseFields(responseFields, swaggerResponsePair.Key, ResolveResponse(swaggerResponsePair.Value, part?.Position), - name!, httpAttributeValues, swaggerOperation.Responses!.Count == 1, part); - } + var name = swaggerOperation.OperationId == null ? null : CodeGenUtility.ToCamelCase(swaggerOperation.OperationId); + if (!ServiceDefinitionUtility.IsValidName(name)) + name = CodeGenUtility.ToCamelCase($"{method} {path}"); - var attributes = new List { new ServiceAttributeInfo("http", httpAttributeValues) }; - if (swaggerOperation.Deprecated.GetValueOrDefault()) - attributes.Add(new ServiceAttributeInfo("obsolete")); - if (swaggerOperation.Tags != null) - attributes.AddRange(swaggerOperation.Tags.Select(x => new ServiceAttributeInfo("tag", new[] { new ServiceAttributeParameterInfo("name", x) }))); - - members.Add(new ServiceMethodInfo( - name: name!, - requestFields: requestFields, - responseFields: responseFields, - attributes: attributes, - summary: PrepareSummary(swaggerOperation.Summary), - remarks: SplitRemarks(swaggerOperation.Description), - parts: part!)); + var httpAttributeValues = new List + { + new ServiceAttributeParameterInfo("method", method), + new ServiceAttributeParameterInfo("path", path), + }; + + var requestFields = new List(); + foreach (var swaggerParameter in swaggerOperationsParameters.EmptyIfNull().Concat(swaggerOperation.Parameters.EmptyIfNull())) + AddRequestFields(requestFields, ResolveParameter(swaggerParameter, part?.Position), name!, method, part); + + var responseFields = new List(); + var swaggerResponsePairs = swaggerOperation.Responses.EmptyIfNull() + .Where(x => x.Key[0] == '2' || x.Key[0] == '3' || !string.IsNullOrEmpty(x.Value.Identifier)).ToList(); + foreach (var swaggerResponsePair in swaggerResponsePairs) + { + AddResponseFields(responseFields, swaggerResponsePair.Key, ResolveResponse(swaggerResponsePair.Value, part?.Position), + name!, httpAttributeValues, swaggerOperation.Responses!.Count == 1, part); } - private void AddRequestFields(IList requestFields, SwaggerParameter swaggerParameter, string serviceMethodName, string httpMethod, ServicePart? part) + var attributes = new List { new ServiceAttributeInfo("http", httpAttributeValues) }; + if (swaggerOperation.Deprecated.GetValueOrDefault()) + attributes.Add(new ServiceAttributeInfo("obsolete")); + if (swaggerOperation.Tags != null) + attributes.AddRange(swaggerOperation.Tags.Select(x => new ServiceAttributeInfo("tag", new[] { new ServiceAttributeParameterInfo("name", x) }))); + + members.Add(new ServiceMethodInfo( + name: name!, + requestFields: requestFields, + responseFields: responseFields, + attributes: attributes, + summary: PrepareSummary(swaggerOperation.Summary), + remarks: SplitRemarks(swaggerOperation.Description), + parts: part!)); + } + + private void AddRequestFields(IList requestFields, SwaggerParameter swaggerParameter, string serviceMethodName, string httpMethod, ServicePart? part) + { + var kind = swaggerParameter.In; + if (kind == SwaggerParameterKind.Path || kind == SwaggerParameterKind.Query || kind == SwaggerParameterKind.Header) { - var kind = swaggerParameter.In; - if (kind == SwaggerParameterKind.Path || kind == SwaggerParameterKind.Query || kind == SwaggerParameterKind.Header) + var typeName = TryGetFacilityTypeName(swaggerParameter, part!.Position); + if (typeName != null) { - var typeName = TryGetFacilityTypeName(swaggerParameter, part!.Position); - if (typeName != null) - { - if (typeName.EndsWith("[]", StringComparison.Ordinal)) - typeName = "string"; + if (typeName.EndsWith("[]", StringComparison.Ordinal)) + typeName = "string"; - var attributes = new List(); + var attributes = new List(); - if (swaggerParameter.Obsolete.GetValueOrDefault()) - attributes.Add(new ServiceAttributeInfo("obsolete")); - if (swaggerParameter.Required.GetValueOrDefault()) - attributes.Add(new ServiceAttributeInfo("required")); + if (swaggerParameter.Obsolete.GetValueOrDefault()) + attributes.Add(new ServiceAttributeInfo("obsolete")); + if (swaggerParameter.Required.GetValueOrDefault()) + attributes.Add(new ServiceAttributeInfo("required")); - var fieldName = swaggerParameter.Identifier ?? swaggerParameter.Name; - if (!ServiceDefinitionUtility.IsValidName(fieldName)) - fieldName = CodeGenUtility.ToCamelCase(fieldName!); + var fieldName = swaggerParameter.Identifier ?? swaggerParameter.Name; + if (!ServiceDefinitionUtility.IsValidName(fieldName)) + fieldName = CodeGenUtility.ToCamelCase(fieldName!); - if (kind == SwaggerParameterKind.Query) - { - var parameters = new List(); - if (httpMethod != "GET") - parameters.Add(new ServiceAttributeParameterInfo("from", "query")); - if (fieldName != swaggerParameter.Name) - parameters.Add(new ServiceAttributeParameterInfo("name", swaggerParameter.Name!)); - if (parameters.Count != 0) - attributes.Add(new ServiceAttributeInfo("http", parameters)); - } - else if (kind == SwaggerParameterKind.Header) - { - attributes.Add(new ServiceAttributeInfo("http", - new[] - { - new ServiceAttributeParameterInfo("from", "header"), - new ServiceAttributeParameterInfo("name", swaggerParameter.Name!), - })); - } - - requestFields.Add(new ServiceFieldInfo( - fieldName!, - typeName: typeName, - attributes: attributes, - summary: PrepareSummary(swaggerParameter.Description), - parts: part)); - } - } - else if (kind == SwaggerParameterKind.Body) - { - var bodySchema = ResolveDefinition(swaggerParameter.Schema!, part!.Position); - if ((bodySchema.Value.Type ?? SwaggerSchemaType.Object) == SwaggerSchemaType.Object && - (bodySchema.Key == null || bodySchema.Key.Equals(serviceMethodName + "Request", StringComparison.OrdinalIgnoreCase))) + if (kind == SwaggerParameterKind.Query) { - AddFieldsFromSchema(requestFields, part, bodySchema); + var parameters = new List(); + if (httpMethod != "GET") + parameters.Add(new ServiceAttributeParameterInfo("from", "query")); + if (fieldName != swaggerParameter.Name) + parameters.Add(new ServiceAttributeParameterInfo("name", swaggerParameter.Name!)); + if (parameters.Count != 0) + attributes.Add(new ServiceAttributeInfo("http", parameters)); } - else + else if (kind == SwaggerParameterKind.Header) { - var typeName = bodySchema.Key ?? FilterBodyTypeName(TryGetFacilityTypeName(bodySchema.Value, part.Position)); - if (typeName != null) - { - requestFields.Add(new ServiceFieldInfo( - bodySchema.Value.Identifier ?? "body", - typeName: typeName, - attributes: new[] { new ServiceAttributeInfo("http", new[] { new ServiceAttributeParameterInfo("from", "body", part) }) }, - summary: PrepareSummary(swaggerParameter.Description), - parts: part)); - } + attributes.Add(new ServiceAttributeInfo("http", + new[] + { + new ServiceAttributeParameterInfo("from", "header"), + new ServiceAttributeParameterInfo("name", swaggerParameter.Name!), + })); } - } - } - private void AddResponseFields(IList responseFields, string statusCode, SwaggerResponse swaggerResponse, string serviceMethodName, IList httpAttributeValues, bool isOnlyResponse, ServicePart? part) - { - var bodySchema = default(KeyValuePair); - - if (swaggerResponse.Schema != null) - bodySchema = ResolveDefinition(swaggerResponse.Schema, part!.Position); - - if (bodySchema.Value != null && (bodySchema.Value.Type ?? SwaggerSchemaType.Object) == SwaggerSchemaType.Object && - (bodySchema.Key == null || bodySchema.Key.Equals(serviceMethodName + "Response", StringComparison.OrdinalIgnoreCase))) - { - httpAttributeValues.Add(new ServiceAttributeParameterInfo("code", statusCode, part!)); - AddFieldsFromSchema(responseFields, part!, bodySchema); - } - else if (swaggerResponse.Identifier == null && isOnlyResponse && swaggerResponse.Schema == null) - { - httpAttributeValues.Add(new ServiceAttributeParameterInfo("code", statusCode, part!)); - } - else - { - responseFields.Add(new ServiceFieldInfo( - swaggerResponse.Identifier ?? (bodySchema.Key == null ? null : CodeGenUtility.ToCamelCase(bodySchema.Key)) ?? GetBodyFieldNameForStatusCode(statusCode), - typeName: bodySchema.Key ?? (bodySchema.Value != null ? FilterBodyTypeName(TryGetFacilityTypeName(bodySchema.Value, part!.Position)) : null) ?? "boolean", - attributes: new[] - { - new ServiceAttributeInfo("http", - new[] - { - new ServiceAttributeParameterInfo("from", "body", part!), - new ServiceAttributeParameterInfo("code", statusCode, part!), - }), - }, - summary: PrepareSummary(swaggerResponse.Description), - parts: part!)); + requestFields.Add(new ServiceFieldInfo( + fieldName!, + typeName: typeName, + attributes: attributes, + summary: PrepareSummary(swaggerParameter.Description), + parts: part)); } } - - private static string GetBodyFieldNameForStatusCode(string statusCode) + else if (kind == SwaggerParameterKind.Body) { - if (int.TryParse(statusCode, out var statusCodeNumber)) + var bodySchema = ResolveDefinition(swaggerParameter.Schema!, part!.Position); + if ((bodySchema.Value.Type ?? SwaggerSchemaType.Object) == SwaggerSchemaType.Object && + (bodySchema.Key == null || bodySchema.Key.Equals(serviceMethodName + "Request", StringComparison.OrdinalIgnoreCase))) { - string name = ((HttpStatusCode) statusCodeNumber).ToString(); - if (name != statusCode) - return CodeGenUtility.ToCamelCase(name); + AddFieldsFromSchema(requestFields, part, bodySchema); } - - return CodeGenUtility.ToCamelCase($"status {statusCode}"); - } - - private void AddFieldsFromSchema(IList requestFields, ServicePart part, KeyValuePair bodySchema) - { - if ((bodySchema.Value.Type ?? SwaggerSchemaType.Object) != SwaggerSchemaType.Object) - throw new NotImplementedException(); - - foreach (var property in bodySchema.Value.Properties.EmptyIfNull()) + else { - var attributes = new List(); - - if (property.Value.Obsolete.GetValueOrDefault()) - attributes.Add(new ServiceAttributeInfo("obsolete")); - if (bodySchema.Value.Required?.Contains(property.Key) == true) - attributes.Add(new ServiceAttributeInfo("required")); - - var typeName = TryGetFacilityTypeName(property.Value, part.Position); + var typeName = bodySchema.Key ?? FilterBodyTypeName(TryGetFacilityTypeName(bodySchema.Value, part.Position)); if (typeName != null) { requestFields.Add(new ServiceFieldInfo( - property.Key, + bodySchema.Value.Identifier ?? "body", typeName: typeName, - attributes: attributes, - summary: PrepareSummary(property.Value.Description), + attributes: new[] { new ServiceAttributeInfo("http", new[] { new ServiceAttributeParameterInfo("from", "body", part) }) }, + summary: PrepareSummary(swaggerParameter.Description), parts: part)); } } } + } - private static string? PrepareSummary(string? summary) => string.IsNullOrWhiteSpace(summary) ? null : Regex.Replace(summary!.Trim(), @"\s+", " "); + private void AddResponseFields(IList responseFields, string statusCode, SwaggerResponse swaggerResponse, string serviceMethodName, IList httpAttributeValues, bool isOnlyResponse, ServicePart? part) + { + var bodySchema = default(KeyValuePair); - private static IReadOnlyList? SplitRemarks(string? remarks) => string.IsNullOrWhiteSpace(remarks) ? null : Regex.Split(remarks!, @"\r?\n"); + if (swaggerResponse.Schema != null) + bodySchema = ResolveDefinition(swaggerResponse.Schema, part!.Position); - private string GetDefinitionNameFromRef(string refValue, ServiceDefinitionPosition? position) + if (bodySchema.Value != null && (bodySchema.Value.Type ?? SwaggerSchemaType.Object) == SwaggerSchemaType.Object && + (bodySchema.Key == null || bodySchema.Key.Equals(serviceMethodName + "Response", StringComparison.OrdinalIgnoreCase))) + { + httpAttributeValues.Add(new ServiceAttributeParameterInfo("code", statusCode, part!)); + AddFieldsFromSchema(responseFields, part!, bodySchema); + } + else if (swaggerResponse.Identifier == null && isOnlyResponse && swaggerResponse.Schema == null) { - const string refPrefix = "#/definitions/"; - if (!refValue.StartsWith(refPrefix, StringComparison.Ordinal)) - m_errors.Add(new ServiceDefinitionError("Definition $ref must start with '#/definitions/'.", position)); - return UnescapeRefPart(refValue.Substring(refPrefix.Length)); + httpAttributeValues.Add(new ServiceAttributeParameterInfo("code", statusCode, part!)); } + else + { + responseFields.Add(new ServiceFieldInfo( + swaggerResponse.Identifier ?? (bodySchema.Key == null ? null : CodeGenUtility.ToCamelCase(bodySchema.Key)) ?? GetBodyFieldNameForStatusCode(statusCode), + typeName: bodySchema.Key ?? (bodySchema.Value != null ? FilterBodyTypeName(TryGetFacilityTypeName(bodySchema.Value, part!.Position)) : null) ?? "boolean", + attributes: new[] + { + new ServiceAttributeInfo("http", + new[] + { + new ServiceAttributeParameterInfo("from", "body", part!), + new ServiceAttributeParameterInfo("code", statusCode, part!), + }), + }, + summary: PrepareSummary(swaggerResponse.Description), + parts: part!)); + } + } - private KeyValuePair ResolveDefinition(SwaggerSchema swaggerDefinition, ServiceDefinitionPosition? position) + private static string GetBodyFieldNameForStatusCode(string statusCode) + { + if (int.TryParse(statusCode, out var statusCodeNumber)) { - string? name = null; + string name = ((HttpStatusCode) statusCodeNumber).ToString(); + if (name != statusCode) + return CodeGenUtility.ToCamelCase(name); + } - if (swaggerDefinition.Ref != null) - { - name = GetDefinitionNameFromRef(swaggerDefinition.Ref, position); - if (!m_swaggerService.Definitions!.TryGetValue(name, out swaggerDefinition)) - m_errors.Add(new ServiceDefinitionError($"Missing definition named '{name}'.", position)); - } + return CodeGenUtility.ToCamelCase($"status {statusCode}"); + } - return new KeyValuePair(name!, swaggerDefinition); - } + private void AddFieldsFromSchema(IList requestFields, ServicePart part, KeyValuePair bodySchema) + { + if ((bodySchema.Value.Type ?? SwaggerSchemaType.Object) != SwaggerSchemaType.Object) + throw new NotImplementedException(); - private void ResolveOperations(ref SwaggerOperations swaggerOperations, ref SwaggerParserContext context) + foreach (var property in bodySchema.Value.Properties.EmptyIfNull()) { - if (swaggerOperations.Ref != null) - { - const string refPrefix = "#/paths/"; - if (!swaggerOperations.Ref.StartsWith(refPrefix, StringComparison.Ordinal)) - m_errors.Add(new ServiceDefinitionError("Operations $ref must start with '#/paths/'.", context.CreatePosition())); + var attributes = new List(); - string name = UnescapeRefPart(swaggerOperations.Ref.Substring(refPrefix.Length)); - if (!m_swaggerService.Paths!.TryGetValue(name, out swaggerOperations)) - m_errors.Add(new ServiceDefinitionError($"Missing path named '{name}'.", context.CreatePosition())); + if (property.Value.Obsolete.GetValueOrDefault()) + attributes.Add(new ServiceAttributeInfo("obsolete")); + if (bodySchema.Value.Required?.Contains(property.Key) == true) + attributes.Add(new ServiceAttributeInfo("required")); - context = context.Root.CreateContext("paths/" + name); + var typeName = TryGetFacilityTypeName(property.Value, part.Position); + if (typeName != null) + { + requestFields.Add(new ServiceFieldInfo( + property.Key, + typeName: typeName, + attributes: attributes, + summary: PrepareSummary(property.Value.Description), + parts: part)); } } + } - private SwaggerParameter ResolveParameter(SwaggerParameter swaggerParameter, ServiceDefinitionPosition? position) + private static string? PrepareSummary(string? summary) => string.IsNullOrWhiteSpace(summary) ? null : Regex.Replace(summary!.Trim(), @"\s+", " "); + + private static IReadOnlyList? SplitRemarks(string? remarks) => string.IsNullOrWhiteSpace(remarks) ? null : Regex.Split(remarks!, @"\r?\n"); + + private string GetDefinitionNameFromRef(string refValue, ServiceDefinitionPosition? position) + { + const string refPrefix = "#/definitions/"; + if (!refValue.StartsWith(refPrefix, StringComparison.Ordinal)) + m_errors.Add(new ServiceDefinitionError("Definition $ref must start with '#/definitions/'.", position)); + return UnescapeRefPart(refValue.Substring(refPrefix.Length)); + } + + private KeyValuePair ResolveDefinition(SwaggerSchema swaggerDefinition, ServiceDefinitionPosition? position) + { + string? name = null; + + if (swaggerDefinition.Ref != null) { - if (swaggerParameter.Ref != null) - { - const string refPrefix = "#/parameters/"; - if (!swaggerParameter.Ref.StartsWith(refPrefix, StringComparison.Ordinal)) - m_errors.Add(new ServiceDefinitionError("Parameter $ref must start with '#/parameters/'.", position)); + name = GetDefinitionNameFromRef(swaggerDefinition.Ref, position); + if (!m_swaggerService.Definitions!.TryGetValue(name, out swaggerDefinition)) + m_errors.Add(new ServiceDefinitionError($"Missing definition named '{name}'.", position)); + } - string name = UnescapeRefPart(swaggerParameter.Ref.Substring(refPrefix.Length)); - if (!m_swaggerService.Parameters!.TryGetValue(name, out swaggerParameter)) - m_errors.Add(new ServiceDefinitionError($"Missing parameter named '{name}'.", position)); - } + return new KeyValuePair(name!, swaggerDefinition); + } - return swaggerParameter; + private void ResolveOperations(ref SwaggerOperations swaggerOperations, ref SwaggerParserContext context) + { + if (swaggerOperations.Ref != null) + { + const string refPrefix = "#/paths/"; + if (!swaggerOperations.Ref.StartsWith(refPrefix, StringComparison.Ordinal)) + m_errors.Add(new ServiceDefinitionError("Operations $ref must start with '#/paths/'.", context.CreatePosition())); + + string name = UnescapeRefPart(swaggerOperations.Ref.Substring(refPrefix.Length)); + if (!m_swaggerService.Paths!.TryGetValue(name, out swaggerOperations)) + m_errors.Add(new ServiceDefinitionError($"Missing path named '{name}'.", context.CreatePosition())); + + context = context.Root.CreateContext("paths/" + name); } + } - private SwaggerResponse ResolveResponse(SwaggerResponse swaggerResponse, ServiceDefinitionPosition? position) + private SwaggerParameter ResolveParameter(SwaggerParameter swaggerParameter, ServiceDefinitionPosition? position) + { + if (swaggerParameter.Ref != null) { - if (swaggerResponse.Ref != null) - { - const string refPrefix = "#/responses/"; - if (!swaggerResponse.Ref.StartsWith(refPrefix, StringComparison.Ordinal)) - m_errors.Add(new ServiceDefinitionError("Response $ref must start with '#/responses/'.", position)); + const string refPrefix = "#/parameters/"; + if (!swaggerParameter.Ref.StartsWith(refPrefix, StringComparison.Ordinal)) + m_errors.Add(new ServiceDefinitionError("Parameter $ref must start with '#/parameters/'.", position)); - string name = UnescapeRefPart(swaggerResponse.Ref.Substring(refPrefix.Length)); - if (!m_swaggerService.Responses!.TryGetValue(name, out swaggerResponse)) - m_errors.Add(new ServiceDefinitionError($"Missing response named '{name}'.", position)); - } + string name = UnescapeRefPart(swaggerParameter.Ref.Substring(refPrefix.Length)); + if (!m_swaggerService.Parameters!.TryGetValue(name, out swaggerParameter)) + m_errors.Add(new ServiceDefinitionError($"Missing parameter named '{name}'.", position)); + } - return swaggerResponse; + return swaggerParameter; + } + + private SwaggerResponse ResolveResponse(SwaggerResponse swaggerResponse, ServiceDefinitionPosition? position) + { + if (swaggerResponse.Ref != null) + { + const string refPrefix = "#/responses/"; + if (!swaggerResponse.Ref.StartsWith(refPrefix, StringComparison.Ordinal)) + m_errors.Add(new ServiceDefinitionError("Response $ref must start with '#/responses/'.", position)); + + string name = UnescapeRefPart(swaggerResponse.Ref.Substring(refPrefix.Length)); + if (!m_swaggerService.Responses!.TryGetValue(name, out swaggerResponse)) + m_errors.Add(new ServiceDefinitionError($"Missing response named '{name}'.", position)); } - private string? TryGetFacilityTypeName(ISwaggerSchema swaggerSchema, ServiceDefinitionPosition? position) + return swaggerResponse; + } + + private string? TryGetFacilityTypeName(ISwaggerSchema swaggerSchema, ServiceDefinitionPosition? position) + { + switch (swaggerSchema.Type ?? SwaggerSchemaType.Object) { - switch (swaggerSchema.Type ?? SwaggerSchemaType.Object) - { - case SwaggerSchemaType.String: - return swaggerSchema.Format == SwaggerSchemaTypeFormat.Byte ? "bytes" : "string"; + case SwaggerSchemaType.String: + return swaggerSchema.Format == SwaggerSchemaTypeFormat.Byte ? "bytes" : "string"; - case SwaggerSchemaType.Number: - return swaggerSchema.Format == SwaggerSchemaTypeFormat.Decimal ? "decimal" : "double"; + case SwaggerSchemaType.Number: + return swaggerSchema.Format == SwaggerSchemaTypeFormat.Decimal ? "decimal" : "double"; - case SwaggerSchemaType.Integer: - return swaggerSchema.Format == SwaggerSchemaTypeFormat.Int64 ? "int64" : "int32"; + case SwaggerSchemaType.Integer: + return swaggerSchema.Format == SwaggerSchemaTypeFormat.Int64 ? "int64" : "int32"; - case SwaggerSchemaType.Boolean: - return "boolean"; + case SwaggerSchemaType.Boolean: + return "boolean"; - case SwaggerSchemaType.Array: - return swaggerSchema.Items?.Type == SwaggerSchemaType.Array ? null : - $"{TryGetFacilityTypeName(swaggerSchema.Items!, position)}[]"; + case SwaggerSchemaType.Array: + return swaggerSchema.Items?.Type == SwaggerSchemaType.Array ? null : + $"{TryGetFacilityTypeName(swaggerSchema.Items!, position)}[]"; - case SwaggerSchemaType.Object: - if (swaggerSchema is SwaggerSchema fullSchema) + case SwaggerSchemaType.Object: + if (swaggerSchema is SwaggerSchema fullSchema) + { + if (fullSchema.Ref != null) { - if (fullSchema.Ref != null) - { - var resolvedSchema = ResolveDefinition(fullSchema, position); + var resolvedSchema = ResolveDefinition(fullSchema, position); - if (IsFacilityError(resolvedSchema)) - return "error"; + if (IsFacilityError(resolvedSchema)) + return "error"; - var resolvedType = resolvedSchema.Value.Type ?? SwaggerSchemaType.Object; - if ((resolvedType == SwaggerSchemaType.String || resolvedType == SwaggerSchemaType.Number || + var resolvedType = resolvedSchema.Value.Type ?? SwaggerSchemaType.Object; + if ((resolvedType == SwaggerSchemaType.String || resolvedType == SwaggerSchemaType.Number || resolvedType == SwaggerSchemaType.Integer || resolvedType == SwaggerSchemaType.Boolean) && - TryGetFacilityTypeName(resolvedSchema.Value, position) is string aliasType) - { - return aliasType; - } - - var resultOfType = TryGetFacilityResultOfType(resolvedSchema, position); - if (resultOfType != null) - return $"result<{resultOfType}>"; - - return resolvedSchema.Key; + TryGetFacilityTypeName(resolvedSchema.Value, position) is string aliasType) + { + return aliasType; } - if (fullSchema.AdditionalProperties != null) - return $"map<{TryGetFacilityTypeName(fullSchema.AdditionalProperties, position)}>"; + var resultOfType = TryGetFacilityResultOfType(resolvedSchema, position); + if (resultOfType != null) + return $"result<{resultOfType}>"; + + return resolvedSchema.Key; } - return "object"; - } + if (fullSchema.AdditionalProperties != null) + return $"map<{TryGetFacilityTypeName(fullSchema.AdditionalProperties, position)}>"; + } - return null; + return "object"; } - internal static string? FilterBodyTypeName(string? typeName) - { - return typeName == "string" || typeName == "bytes" || typeName == "int32" || typeName == "int64" || typeName == "double" || typeName == "decimal" ? null : typeName; - } + return null; + } - internal static bool IsFacilityError(KeyValuePair swaggerSchema) - { - return swaggerSchema.Key == "Error" && - swaggerSchema.Value.Properties.EmptyIfNull().Any(x => x.Key == "code" && x.Value.Type == SwaggerSchemaType.String) && - swaggerSchema.Value.Properties.EmptyIfNull().Any(x => x.Key == "message" && x.Value.Type == SwaggerSchemaType.String); - } + internal static string? FilterBodyTypeName(string? typeName) + { + return typeName == "string" || typeName == "bytes" || typeName == "int32" || typeName == "int64" || typeName == "double" || typeName == "decimal" ? null : typeName; + } - internal string? TryGetFacilityResultOfType(KeyValuePair swaggerSchema, ServiceDefinitionPosition? position) - { - const string nameSuffix = "Result"; - if (!swaggerSchema.Key!.EndsWith(nameSuffix, StringComparison.Ordinal)) - return null; + internal static bool IsFacilityError(KeyValuePair swaggerSchema) + { + return swaggerSchema.Key == "Error" && + swaggerSchema.Value.Properties.EmptyIfNull().Any(x => x.Key == "code" && x.Value.Type == SwaggerSchemaType.String) && + swaggerSchema.Value.Properties.EmptyIfNull().Any(x => x.Key == "message" && x.Value.Type == SwaggerSchemaType.String); + } - var properties = swaggerSchema.Value.Properties.EmptyIfNull(); - if (!properties.Any(x => x.Key == "error" && x.Value.Ref == "#/definitions/Error")) - return null; + internal string? TryGetFacilityResultOfType(KeyValuePair swaggerSchema, ServiceDefinitionPosition? position) + { + const string nameSuffix = "Result"; + if (!swaggerSchema.Key!.EndsWith(nameSuffix, StringComparison.Ordinal)) + return null; - var valueSchema = properties.Where(x => x.Key == "value").Select(x => x.Value).FirstOrDefault(); - if (valueSchema == null) - return null; + var properties = swaggerSchema.Value.Properties.EmptyIfNull(); + if (!properties.Any(x => x.Key == "error" && x.Value.Ref == "#/definitions/Error")) + return null; - return TryGetFacilityTypeName(valueSchema, position); - } + var valueSchema = properties.Where(x => x.Key == "value").Select(x => x.Value).FirstOrDefault(); + if (valueSchema == null) + return null; - private static string UnescapeRefPart(string value) => value.Replace("~1", "/").Replace("~0", "~"); + return TryGetFacilityTypeName(valueSchema, position); + } - private static readonly Regex s_pathParameter = new Regex(@"\{[^}]+\}"); + private static string UnescapeRefPart(string value) => value.Replace("~1", "/").Replace("~0", "~"); - private readonly SwaggerService m_swaggerService; - private readonly string? m_serviceName; - private readonly List m_errors; - } + private static readonly Regex s_pathParameter = new Regex(@"\{[^}]+\}"); + + private readonly SwaggerService m_swaggerService; + private readonly string? m_serviceName; + private readonly List m_errors; } diff --git a/src/Facility.Definition.Swagger/SwaggerExternalDocumentation.cs b/src/Facility.Definition.Swagger/SwaggerExternalDocumentation.cs index ba1f328..ff31740 100644 --- a/src/Facility.Definition.Swagger/SwaggerExternalDocumentation.cs +++ b/src/Facility.Definition.Swagger/SwaggerExternalDocumentation.cs @@ -1,11 +1,8 @@ -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public class SwaggerExternalDocumentation { - public class SwaggerExternalDocumentation - { - public string? Description { get; set; } + public string? Description { get; set; } - public string? Url { get; set; } - } + public string? Url { get; set; } } diff --git a/src/Facility.Definition.Swagger/SwaggerGenerator.cs b/src/Facility.Definition.Swagger/SwaggerGenerator.cs index ce043cb..0e9a59b 100644 --- a/src/Facility.Definition.Swagger/SwaggerGenerator.cs +++ b/src/Facility.Definition.Swagger/SwaggerGenerator.cs @@ -1,8 +1,5 @@ -using System; using System.Collections; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using Facility.Definition.CodeGen; using Facility.Definition.Fsd; using Facility.Definition.Http; @@ -12,621 +9,619 @@ using YamlDotNet.Serialization; using YamlDotNet.Serialization.EventEmitters; -namespace Facility.Definition.Swagger +namespace Facility.Definition.Swagger; + +/// +/// Generates a Swagger (OpenAPI) 2.0 file for a service definition. +/// +public sealed class SwaggerGenerator : CodeGenerator { /// - /// Generates a Swagger (OpenAPI) 2.0 file for a service definition. + /// Generates Swagger. /// - public sealed class SwaggerGenerator : CodeGenerator - { - /// - /// Generates Swagger. - /// - /// The settings. - /// The number of updated files. - public static int GenerateSwagger(SwaggerGeneratorSettings settings) => - FileGenerator.GenerateFiles( - new SwaggerParser { ServiceName = settings.ServiceName }, - new SwaggerGenerator { GeneratorName = nameof(SwaggerGenerator) }, - settings); - - /// - /// True to generate a Facility Service Definition (instead of Swagger). - /// - public bool GeneratesFsd { get; set; } - - /// - /// True to generate JSON (instead of YAML). - /// - public bool GeneratesJson { get; set; } - - /// - /// Generates Swagger (OpenAPI 2.0) for a service definition. - /// - public SwaggerService GenerateSwaggerService(ServiceInfo service) - { - var httpServiceInfo = HttpServiceInfo.Create(service); - - var swaggerService = new SwaggerService - { - Swagger = SwaggerUtility.SwaggerVersion, - Info = new SwaggerInfo - { - Identifier = service.Name, - Title = GetSummaryOrNull(service) ?? service.Name, - Description = GetRemarksOrNull(service), - Version = service.TryGetAttribute("info")?.TryGetParameterValue("version") ?? "0.0.0", - CodeGen = CodeGenUtility.GetCodeGenComment(GeneratorName ?? ""), - }, - }; - - var defaultBaseUri = httpServiceInfo.Url; - if (defaultBaseUri != null) - { - var baseUri = new Uri(defaultBaseUri); - swaggerService.Host = baseUri.Host; - swaggerService.Schemes = new[] { baseUri.Scheme }; - - string basePath = baseUri.PathAndQuery; - if (!string.IsNullOrEmpty(basePath) && basePath != "/") - swaggerService.BasePath = baseUri.PathAndQuery; - } + /// The settings. + /// The number of updated files. + public static int GenerateSwagger(SwaggerGeneratorSettings settings) => + FileGenerator.GenerateFiles( + new SwaggerParser { ServiceName = settings.ServiceName }, + new SwaggerGenerator { GeneratorName = nameof(SwaggerGenerator) }, + settings); - var paths = new OurDictionary(); - foreach (var httpMethodInfo in httpServiceInfo.Methods) - AddMethodToPaths(paths, service, httpMethodInfo); - swaggerService.Paths = paths; - - var dtoInfos = new OurDictionary(); - foreach (var httpMethodInfo in httpServiceInfo.Methods) - { - if (httpMethodInfo.RequestBodyField != null) - AddDtos(dtoInfos, GetDtosForType(service.GetFieldType(httpMethodInfo.RequestBodyField.ServiceField)!)); - - AddDto(dtoInfos, TryCreateMethodRequestBodyType(httpMethodInfo)?.Dto); - - foreach (var httpResponseInfo in httpMethodInfo.ValidResponses) - { - if (httpResponseInfo.BodyField != null) - AddDtos(dtoInfos, GetDtosForType(service.GetFieldType(httpResponseInfo.BodyField.ServiceField)!)); + /// + /// True to generate a Facility Service Definition (instead of Swagger). + /// + public bool GeneratesFsd { get; set; } - AddDto(dtoInfos, TryCreateMethodResponseBodyType(httpMethodInfo, httpResponseInfo)?.Dto); - } - } + /// + /// True to generate JSON (instead of YAML). + /// + public bool GeneratesJson { get; set; } - while (true) - { - var dtoCount = dtoInfos.Count; - foreach (var field in dtoInfos.Values.SelectMany(x => x.Fields).ToList()) - AddDtos(dtoInfos, GetDtosForType(service.GetFieldType(field)!)); - if (dtoCount == dtoInfos.Count) - break; - } + /// + /// Generates Swagger (OpenAPI 2.0) for a service definition. + /// + public SwaggerService GenerateSwaggerService(ServiceInfo service) + { + var httpServiceInfo = HttpServiceInfo.Create(service); - var definitions = new OurDictionary(); - foreach (var dtoInfo in dtoInfos.Values) - definitions[dtoInfo.Name] = GetDtoSchema(service, dtoInfo); - swaggerService.Definitions = definitions.Count == 0 ? null : definitions; + var swaggerService = new SwaggerService + { + Swagger = SwaggerUtility.SwaggerVersion, + Info = new SwaggerInfo + { + Identifier = service.Name, + Title = GetSummaryOrNull(service) ?? service.Name, + Description = GetRemarksOrNull(service), + Version = service.TryGetAttribute("info")?.TryGetParameterValue("version") ?? "0.0.0", + CodeGen = CodeGenUtility.GetCodeGenComment(GeneratorName ?? ""), + }, + }; + + var defaultBaseUri = httpServiceInfo.Url; + if (defaultBaseUri != null) + { + var baseUri = new Uri(defaultBaseUri); + swaggerService.Host = baseUri.Host; + swaggerService.Schemes = new[] { baseUri.Scheme }; - return swaggerService; + string basePath = baseUri.PathAndQuery; + if (!string.IsNullOrEmpty(basePath) && basePath != "/") + swaggerService.BasePath = baseUri.PathAndQuery; } - /// - /// Generates a Swagger (OpenAPI 2.0) file for a service definition. - /// - public override CodeGenOutput GenerateOutput(ServiceInfo service) + var paths = new OurDictionary(); + foreach (var httpMethodInfo in httpServiceInfo.Methods) + AddMethodToPaths(paths, service, httpMethodInfo); + swaggerService.Paths = paths; + + var dtoInfos = new OurDictionary(); + foreach (var httpMethodInfo in httpServiceInfo.Methods) { - if (GeneratesFsd) - return new FsdGenerator { GeneratorName = GeneratorName, IndentText = IndentText, NewLine = NewLine }.GenerateOutput(service); + if (httpMethodInfo.RequestBodyField != null) + AddDtos(dtoInfos, GetDtosForType(service.GetFieldType(httpMethodInfo.RequestBodyField.ServiceField)!)); - var swaggerService = GenerateSwaggerService(service); + AddDto(dtoInfos, TryCreateMethodRequestBodyType(httpMethodInfo)?.Dto); - if (GeneratesJson) + foreach (var httpResponseInfo in httpMethodInfo.ValidResponses) { - return new CodeGenOutput(CreateFile($"{service.Name}.json", code => - { - using var jsonTextWriter = new JsonTextWriter(code.TextWriter) { Formatting = Formatting.Indented, CloseOutput = false }; - JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings).Serialize(jsonTextWriter, swaggerService); - })); - } - else - { - return new CodeGenOutput(CreateFile($"{service.Name}.yaml", code => - { - var yamlObject = ConvertJTokenToObject(JToken.FromObject(swaggerService, JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings)))!; - new SerializerBuilder().DisableAliases().WithEventEmitter(x => new OurEventEmitter(x)).Build().Serialize(code.TextWriter, yamlObject); - })); + if (httpResponseInfo.BodyField != null) + AddDtos(dtoInfos, GetDtosForType(service.GetFieldType(httpResponseInfo.BodyField.ServiceField)!)); + + AddDto(dtoInfos, TryCreateMethodResponseBodyType(httpMethodInfo, httpResponseInfo)?.Dto); } } - /// - /// Applies generator-specific settings. - /// - public override void ApplySettings(FileGeneratorSettings settings) + while (true) { - var swaggerSettings = (SwaggerGeneratorSettings) settings; - GeneratesFsd = swaggerSettings.GeneratesFsd; - GeneratesJson = swaggerSettings.GeneratesJson; + var dtoCount = dtoInfos.Count; + foreach (var field in dtoInfos.Values.SelectMany(x => x.Fields).ToList()) + AddDtos(dtoInfos, GetDtosForType(service.GetFieldType(field)!)); + if (dtoCount == dtoInfos.Count) + break; } - /// - /// Supports single output. - /// - public override bool SupportsSingleOutput => true; + var definitions = new OurDictionary(); + foreach (var dtoInfo in dtoInfos.Values) + definitions[dtoInfo.Name] = GetDtoSchema(service, dtoInfo); + swaggerService.Definitions = definitions.Count == 0 ? null : definitions; - private void AddDtos(IDictionary dictionary, IEnumerable dtos) - { - foreach (var dto in dtos) - AddDto(dictionary, dto); - } + return swaggerService; + } + + /// + /// Generates a Swagger (OpenAPI 2.0) file for a service definition. + /// + public override CodeGenOutput GenerateOutput(ServiceInfo service) + { + if (GeneratesFsd) + return new FsdGenerator { GeneratorName = GeneratorName, IndentText = IndentText, NewLine = NewLine }.GenerateOutput(service); + + var swaggerService = GenerateSwaggerService(service); - private void AddDto(IDictionary dictionary, ServiceDtoInfo? dto) + if (GeneratesJson) { - if (dto != null && !dictionary.ContainsKey(dto.Name)) - dictionary[dto.Name] = dto; + return new CodeGenOutput(CreateFile($"{service.Name}.json", code => + { + using var jsonTextWriter = new JsonTextWriter(code.TextWriter) { Formatting = Formatting.Indented, CloseOutput = false }; + JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings).Serialize(jsonTextWriter, swaggerService); + })); } - - private static ServiceTypeInfo? TryCreateMethodRequestBodyType(HttpMethodInfo httpMethodInfo) + else { - if (httpMethodInfo.RequestNormalFields.Count == 0) - return null; - - return ServiceTypeInfo.CreateDto(new ServiceDtoInfo( - name: $"{CodeGenUtility.ToPascalCase(httpMethodInfo.ServiceMethod.Name)}Request", - fields: httpMethodInfo.RequestNormalFields.Select(x => x.ServiceField))); + return new CodeGenOutput(CreateFile($"{service.Name}.yaml", code => + { + var yamlObject = ConvertJTokenToObject(JToken.FromObject(swaggerService, JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings)))!; + new SerializerBuilder().DisableAliases().WithEventEmitter(x => new OurEventEmitter(x)).Build().Serialize(code.TextWriter, yamlObject); + })); } + } - private static ServiceTypeInfo? TryCreateMethodResponseBodyType(HttpMethodInfo httpMethodInfo, HttpResponseInfo httpResponseInfo) - { - if (httpResponseInfo.NormalFields == null || httpResponseInfo.NormalFields.Count == 0) - return null; + /// + /// Applies generator-specific settings. + /// + public override void ApplySettings(FileGeneratorSettings settings) + { + var swaggerSettings = (SwaggerGeneratorSettings) settings; + GeneratesFsd = swaggerSettings.GeneratesFsd; + GeneratesJson = swaggerSettings.GeneratesJson; + } - return ServiceTypeInfo.CreateDto(new ServiceDtoInfo( - name: $"{CodeGenUtility.ToPascalCase(httpMethodInfo.ServiceMethod.Name)}Response", - fields: httpResponseInfo.NormalFields.Select(x => x.ServiceField))); - } + /// + /// Supports single output. + /// + public override bool SupportsSingleOutput => true; - private IEnumerable GetDtosForType(ServiceTypeInfo type) - { - switch (type.Kind) - { - case ServiceTypeKind.Error: - yield return GetErrorDto(); - break; - case ServiceTypeKind.Dto: - yield return type.Dto!; - break; - case ServiceTypeKind.Result: - yield return GetResultDto(type); - break; - } + private void AddDtos(IDictionary dictionary, IEnumerable dtos) + { + foreach (var dto in dtos) + AddDto(dictionary, dto); + } - if (type.ValueType != null) - { - foreach (var dto in GetDtosForType(type.ValueType)) - yield return dto; - } - } + private void AddDto(IDictionary dictionary, ServiceDtoInfo? dto) + { + if (dto != null && !dictionary.ContainsKey(dto.Name)) + dictionary[dto.Name] = dto; + } - private static string? GetSummaryOrNull(IServiceHasSummary info) => info.Summary.Length == 0 ? null : info.Summary; + private static ServiceTypeInfo? TryCreateMethodRequestBodyType(HttpMethodInfo httpMethodInfo) + { + if (httpMethodInfo.RequestNormalFields.Count == 0) + return null; - private static string? GetRemarksOrNull(ServiceMemberInfo info) => info.Remarks.Count == 0 ? null : string.Join("\n", info.Remarks); + return ServiceTypeInfo.CreateDto(new ServiceDtoInfo( + name: $"{CodeGenUtility.ToPascalCase(httpMethodInfo.ServiceMethod.Name)}Request", + fields: httpMethodInfo.RequestNormalFields.Select(x => x.ServiceField))); + } - private static bool? GetObsoleteOrNull(ServiceElementWithAttributesInfo info) => info.IsObsolete ? true : default(bool?); + private static ServiceTypeInfo? TryCreateMethodResponseBodyType(HttpMethodInfo httpMethodInfo, HttpResponseInfo httpResponseInfo) + { + if (httpResponseInfo.NormalFields == null || httpResponseInfo.NormalFields.Count == 0) + return null; - private static ServiceDtoInfo GetErrorDto() - { - return new ServiceDtoInfo(name: "Error", - fields: new[] - { - new ServiceFieldInfo(name: "code", typeName: "string", summary: "The error code."), - new ServiceFieldInfo(name: "message", typeName: "string", summary: "The error message."), - new ServiceFieldInfo(name: "details", typeName: "object", summary: "Advanced error details."), - new ServiceFieldInfo(name: "innerError", typeName: "error", summary: "The inner error."), - }, - summary: "An error."); - } + return ServiceTypeInfo.CreateDto(new ServiceDtoInfo( + name: $"{CodeGenUtility.ToPascalCase(httpMethodInfo.ServiceMethod.Name)}Response", + fields: httpResponseInfo.NormalFields.Select(x => x.ServiceField))); + } - private static string GetTypeAsDtoName(ServiceTypeInfo type) + private IEnumerable GetDtosForType(ServiceTypeInfo type) + { + switch (type.Kind) { - var typeKind = type.Kind; - switch (typeKind) - { - case ServiceTypeKind.Dto: - return type.Dto!.Name; - case ServiceTypeKind.Enum: - return type.Enum!.Name; - case ServiceTypeKind.Result: - case ServiceTypeKind.Array: - case ServiceTypeKind.Map: - return GetTypeAsDtoName(type.ValueType!) + typeKind; - default: - return typeKind.ToString(); - } + case ServiceTypeKind.Error: + yield return GetErrorDto(); + break; + case ServiceTypeKind.Dto: + yield return type.Dto!; + break; + case ServiceTypeKind.Result: + yield return GetResultDto(type); + break; } - private static ServiceDtoInfo GetResultDto(ServiceTypeInfo type) + if (type.ValueType != null) { - return new ServiceDtoInfo(name: GetTypeAsDtoName(type), - fields: new[] - { - new ServiceFieldInfo(name: "value", typeName: type.ValueType!.ToString(), summary: "The value."), - new ServiceFieldInfo(name: "error", typeName: "error", summary: "The error."), - }, - summary: "A result value or error."); + foreach (var dto in GetDtosForType(type.ValueType)) + yield return dto; } + } - private static void AddMethodToPaths(IDictionary paths, ServiceInfo service, HttpMethodInfo httpMethodInfo) - { - var methodInfo = httpMethodInfo.ServiceMethod; + private static string? GetSummaryOrNull(IServiceHasSummary info) => info.Summary.Length == 0 ? null : info.Summary; - if (!paths.TryGetValue(httpMethodInfo.Path, out var operations)) - paths[httpMethodInfo.Path] = operations = new SwaggerOperations(); + private static string? GetRemarksOrNull(ServiceMemberInfo info) => info.Remarks.Count == 0 ? null : string.Join("\n", info.Remarks); - var operation = new SwaggerOperation + private static bool? GetObsoleteOrNull(ServiceElementWithAttributesInfo info) => info.IsObsolete ? true : default(bool?); + + private static ServiceDtoInfo GetErrorDto() + { + return new ServiceDtoInfo(name: "Error", + fields: new[] + { + new ServiceFieldInfo(name: "code", typeName: "string", summary: "The error code."), + new ServiceFieldInfo(name: "message", typeName: "string", summary: "The error message."), + new ServiceFieldInfo(name: "details", typeName: "object", summary: "Advanced error details."), + new ServiceFieldInfo(name: "innerError", typeName: "error", summary: "The inner error."), + }, + summary: "An error."); + } + + private static string GetTypeAsDtoName(ServiceTypeInfo type) + { + var typeKind = type.Kind; + switch (typeKind) + { + case ServiceTypeKind.Dto: + return type.Dto!.Name; + case ServiceTypeKind.Enum: + return type.Enum!.Name; + case ServiceTypeKind.Result: + case ServiceTypeKind.Array: + case ServiceTypeKind.Map: + return GetTypeAsDtoName(type.ValueType!) + typeKind; + default: + return typeKind.ToString(); + } + } + + private static ServiceDtoInfo GetResultDto(ServiceTypeInfo type) + { + return new ServiceDtoInfo(name: GetTypeAsDtoName(type), + fields: new[] { - Summary = GetSummaryOrNull(methodInfo), - Description = GetRemarksOrNull(methodInfo), - OperationId = methodInfo.Name, - Deprecated = GetObsoleteOrNull(methodInfo), - Tags = methodInfo.TagNames.Count == 0 ? null : methodInfo.TagNames.ToList(), - }; + new ServiceFieldInfo(name: "value", typeName: type.ValueType!.ToString(), summary: "The value."), + new ServiceFieldInfo(name: "error", typeName: "error", summary: "The error."), + }, + summary: "A result value or error."); + } - if (httpMethodInfo.RequestNormalFields.Count != 0 || httpMethodInfo.RequestBodyField != null) - operation.Consumes = new[] { "application/json" }; - if (httpMethodInfo.ValidResponses.Any(x => (x.NormalFields != null && x.NormalFields.Count != 0) || (x.BodyField != null && service.GetFieldType(x.BodyField.ServiceField)!.Kind != ServiceTypeKind.Boolean))) - operation.Produces = new[] { "application/json" }; + private static void AddMethodToPaths(IDictionary paths, ServiceInfo service, HttpMethodInfo httpMethodInfo) + { + var methodInfo = httpMethodInfo.ServiceMethod; - var parameters = new List(); + if (!paths.TryGetValue(httpMethodInfo.Path, out var operations)) + paths[httpMethodInfo.Path] = operations = new SwaggerOperations(); - foreach (var httpPathInfo in httpMethodInfo.PathFields) - parameters.Add(CreateSwaggerParameter(service, httpPathInfo.ServiceField, SwaggerParameterKind.Path, httpPathInfo.Name)); + var operation = new SwaggerOperation + { + Summary = GetSummaryOrNull(methodInfo), + Description = GetRemarksOrNull(methodInfo), + OperationId = methodInfo.Name, + Deprecated = GetObsoleteOrNull(methodInfo), + Tags = methodInfo.TagNames.Count == 0 ? null : methodInfo.TagNames.ToList(), + }; - foreach (var httpQueryInfo in httpMethodInfo.QueryFields) - parameters.Add(CreateSwaggerParameter(service, httpQueryInfo.ServiceField, SwaggerParameterKind.Query, httpQueryInfo.Name)); + if (httpMethodInfo.RequestNormalFields.Count != 0 || httpMethodInfo.RequestBodyField != null) + operation.Consumes = new[] { "application/json" }; + if (httpMethodInfo.ValidResponses.Any(x => (x.NormalFields != null && x.NormalFields.Count != 0) || (x.BodyField != null && service.GetFieldType(x.BodyField.ServiceField)!.Kind != ServiceTypeKind.Boolean))) + operation.Produces = new[] { "application/json" }; - foreach (var httpHeaderInfo in httpMethodInfo.RequestHeaderFields) - parameters.Add(CreateSwaggerParameter(service, httpHeaderInfo.ServiceField, SwaggerParameterKind.Header, httpHeaderInfo.Name)); + var parameters = new List(); - var requestBodyFieldType = httpMethodInfo.RequestBodyField == null ? null : service.GetFieldType(httpMethodInfo.RequestBodyField.ServiceField); - if (requestBodyFieldType != null && requestBodyFieldType.Kind != ServiceTypeKind.Boolean) - parameters.Add(CreateSwaggerRequestBodyParameter(requestBodyFieldType, "request", httpMethodInfo.RequestBodyField!.ServiceField.Summary)); - else if (httpMethodInfo.RequestNormalFields.Count != 0) - parameters.Add(CreateSwaggerRequestBodyParameter(TryCreateMethodRequestBodyType(httpMethodInfo)!, "request")); + foreach (var httpPathInfo in httpMethodInfo.PathFields) + parameters.Add(CreateSwaggerParameter(service, httpPathInfo.ServiceField, SwaggerParameterKind.Path, httpPathInfo.Name)); - if (parameters.Count != 0) - operation.Parameters = parameters; + foreach (var httpQueryInfo in httpMethodInfo.QueryFields) + parameters.Add(CreateSwaggerParameter(service, httpQueryInfo.ServiceField, SwaggerParameterKind.Query, httpQueryInfo.Name)); - var responses = new OurDictionary(); + foreach (var httpHeaderInfo in httpMethodInfo.RequestHeaderFields) + parameters.Add(CreateSwaggerParameter(service, httpHeaderInfo.ServiceField, SwaggerParameterKind.Header, httpHeaderInfo.Name)); - foreach (var validResponse in httpMethodInfo.ValidResponses) - { - string statusCodeString = ((int) validResponse.StatusCode).ToString(CultureInfo.InvariantCulture); - - var bodyField = validResponse.BodyField; - var bodyFieldType = bodyField == null ? null : service.GetFieldType(bodyField.ServiceField); - if (bodyField != null) - responses[statusCodeString] = CreateSwaggerResponse(bodyFieldType, bodyField.ServiceField.Name, bodyField.ServiceField.Summary); - else if (validResponse.NormalFields != null && validResponse.NormalFields.Count != 0) - responses[statusCodeString] = CreateSwaggerResponse(TryCreateMethodResponseBodyType(httpMethodInfo, validResponse)); - else - responses[statusCodeString] = CreateSwaggerResponse(); - } + var requestBodyFieldType = httpMethodInfo.RequestBodyField == null ? null : service.GetFieldType(httpMethodInfo.RequestBodyField.ServiceField); + if (requestBodyFieldType != null && requestBodyFieldType.Kind != ServiceTypeKind.Boolean) + parameters.Add(CreateSwaggerRequestBodyParameter(requestBodyFieldType, "request", httpMethodInfo.RequestBodyField!.ServiceField.Summary)); + else if (httpMethodInfo.RequestNormalFields.Count != 0) + parameters.Add(CreateSwaggerRequestBodyParameter(TryCreateMethodRequestBodyType(httpMethodInfo)!, "request")); - operation.Responses = responses; + if (parameters.Count != 0) + operation.Parameters = parameters; - string httpMethod = httpMethodInfo.Method.ToLowerInvariant(); - switch (httpMethod) - { - case "get": - operations.Get = operation; - break; - case "post": - operations.Post = operation; - break; - case "put": - operations.Put = operation; - break; - case "delete": - operations.Delete = operation; - break; - case "options": - operations.Options = operation; - break; - case "head": - operations.Head = operation; - break; - case "patch": - operations.Patch = operation; - break; - default: - throw new InvalidOperationException("Unexpected HTTP method: " + httpMethod); - } - } + var responses = new OurDictionary(); - private static SwaggerParameter CreateSwaggerParameter(ServiceInfo service, ServiceFieldInfo fieldInfo, string inKind, string? name) - { - var parameterObject = GetTypeSchema(service.GetFieldType(fieldInfo)!); - parameterObject.In = inKind; - parameterObject.Name = name ?? fieldInfo.Name; - if (parameterObject.Name != fieldInfo.Name) - parameterObject.Identifier = fieldInfo.Name; - parameterObject.Description = GetSummaryOrNull(fieldInfo); - parameterObject.Required = fieldInfo.IsRequired || inKind == SwaggerParameterKind.Path ? true : default(bool?); - parameterObject.Obsolete = GetObsoleteOrNull(fieldInfo); - return parameterObject; + foreach (var validResponse in httpMethodInfo.ValidResponses) + { + string statusCodeString = ((int) validResponse.StatusCode).ToString(CultureInfo.InvariantCulture); + + var bodyField = validResponse.BodyField; + var bodyFieldType = bodyField == null ? null : service.GetFieldType(bodyField.ServiceField); + if (bodyField != null) + responses[statusCodeString] = CreateSwaggerResponse(bodyFieldType, bodyField.ServiceField.Name, bodyField.ServiceField.Summary); + else if (validResponse.NormalFields != null && validResponse.NormalFields.Count != 0) + responses[statusCodeString] = CreateSwaggerResponse(TryCreateMethodResponseBodyType(httpMethodInfo, validResponse)); + else + responses[statusCodeString] = CreateSwaggerResponse(); } - private static SwaggerParameter CreateSwaggerRequestBodyParameter(ServiceTypeInfo type, string name, string? description = null) + operation.Responses = responses; + + string httpMethod = httpMethodInfo.Method.ToLowerInvariant(); + switch (httpMethod) { - return new SwaggerParameter - { - In = SwaggerParameterKind.Body, - Name = name, - Description = description, - Required = true, - Schema = GetTypeSchema(type), - }; + case "get": + operations.Get = operation; + break; + case "post": + operations.Post = operation; + break; + case "put": + operations.Put = operation; + break; + case "delete": + operations.Delete = operation; + break; + case "options": + operations.Options = operation; + break; + case "head": + operations.Head = operation; + break; + case "patch": + operations.Patch = operation; + break; + default: + throw new InvalidOperationException("Unexpected HTTP method: " + httpMethod); } + } - private static SwaggerResponse CreateSwaggerResponse(ServiceTypeInfo? type = null, string? identifier = null, string? description = null) + private static SwaggerParameter CreateSwaggerParameter(ServiceInfo service, ServiceFieldInfo fieldInfo, string inKind, string? name) + { + var parameterObject = GetTypeSchema(service.GetFieldType(fieldInfo)!); + parameterObject.In = inKind; + parameterObject.Name = name ?? fieldInfo.Name; + if (parameterObject.Name != fieldInfo.Name) + parameterObject.Identifier = fieldInfo.Name; + parameterObject.Description = GetSummaryOrNull(fieldInfo); + parameterObject.Required = fieldInfo.IsRequired || inKind == SwaggerParameterKind.Path ? true : default(bool?); + parameterObject.Obsolete = GetObsoleteOrNull(fieldInfo); + return parameterObject; + } + + private static SwaggerParameter CreateSwaggerRequestBodyParameter(ServiceTypeInfo type, string name, string? description = null) + { + return new SwaggerParameter { - return new SwaggerResponse - { - Description = description ?? "", - Schema = type != null && type.Kind != ServiceTypeKind.Boolean ? GetTypeSchema(type) : null, - Identifier = identifier, - }; - } + In = SwaggerParameterKind.Body, + Name = name, + Description = description, + Required = true, + Schema = GetTypeSchema(type), + }; + } - private static T GetTypeSchema(ServiceTypeInfo type) - where T : ISwaggerSchema, new() + private static SwaggerResponse CreateSwaggerResponse(ServiceTypeInfo? type = null, string? identifier = null, string? description = null) + { + return new SwaggerResponse { - switch (type.Kind) - { - case ServiceTypeKind.String: - return new T { Type = SwaggerSchemaType.String }; - case ServiceTypeKind.Boolean: - return new T { Type = SwaggerSchemaType.Boolean }; - case ServiceTypeKind.Double: - return new T { Type = SwaggerSchemaType.Number, Format = SwaggerSchemaTypeFormat.Double }; - case ServiceTypeKind.Int32: - return new T { Type = SwaggerSchemaType.Integer, Format = SwaggerSchemaTypeFormat.Int32 }; - case ServiceTypeKind.Int64: - return new T { Type = SwaggerSchemaType.Integer, Format = SwaggerSchemaTypeFormat.Int64 }; - case ServiceTypeKind.Decimal: - return new T { Type = SwaggerSchemaType.Number, Format = SwaggerSchemaTypeFormat.Decimal }; - case ServiceTypeKind.Bytes: - return new T { Type = SwaggerSchemaType.String, Format = SwaggerSchemaTypeFormat.Byte }; - case ServiceTypeKind.Object: - return new T { Type = SwaggerSchemaType.Object }; - case ServiceTypeKind.Error: - return GetErrorSchemaRef(); - case ServiceTypeKind.Dto: - return GetDtoSchemaRef(type.Dto!); - case ServiceTypeKind.Enum: - return GetEnumSchema(type.Enum!); - case ServiceTypeKind.Result: - return GetResultTypeRef(type); - case ServiceTypeKind.Array: - return GetArrayOfSchema(type.ValueType!); - case ServiceTypeKind.Map: - return (T) (object) GetMapOfSchema(type.ValueType!); - default: - throw new InvalidOperationException("Unexpected field type kind: " + type.Kind); - } - } + Description = description ?? "", + Schema = type != null && type.Kind != ServiceTypeKind.Boolean ? GetTypeSchema(type) : null, + Identifier = identifier, + }; + } - private static SwaggerSchema GetDtoSchema(ServiceInfo serviceInfo, ServiceDtoInfo dtoInfo) + private static T GetTypeSchema(ServiceTypeInfo type) + where T : ISwaggerSchema, new() + { + switch (type.Kind) { - var propertiesObject = new OurDictionary(); - List? requiredFieldNames = null; + case ServiceTypeKind.String: + return new T { Type = SwaggerSchemaType.String }; + case ServiceTypeKind.Boolean: + return new T { Type = SwaggerSchemaType.Boolean }; + case ServiceTypeKind.Double: + return new T { Type = SwaggerSchemaType.Number, Format = SwaggerSchemaTypeFormat.Double }; + case ServiceTypeKind.Int32: + return new T { Type = SwaggerSchemaType.Integer, Format = SwaggerSchemaTypeFormat.Int32 }; + case ServiceTypeKind.Int64: + return new T { Type = SwaggerSchemaType.Integer, Format = SwaggerSchemaTypeFormat.Int64 }; + case ServiceTypeKind.Decimal: + return new T { Type = SwaggerSchemaType.Number, Format = SwaggerSchemaTypeFormat.Decimal }; + case ServiceTypeKind.Bytes: + return new T { Type = SwaggerSchemaType.String, Format = SwaggerSchemaTypeFormat.Byte }; + case ServiceTypeKind.Object: + return new T { Type = SwaggerSchemaType.Object }; + case ServiceTypeKind.Error: + return GetErrorSchemaRef(); + case ServiceTypeKind.Dto: + return GetDtoSchemaRef(type.Dto!); + case ServiceTypeKind.Enum: + return GetEnumSchema(type.Enum!); + case ServiceTypeKind.Result: + return GetResultTypeRef(type); + case ServiceTypeKind.Array: + return GetArrayOfSchema(type.ValueType!); + case ServiceTypeKind.Map: + return (T) (object) GetMapOfSchema(type.ValueType!); + default: + throw new InvalidOperationException("Unexpected field type kind: " + type.Kind); + } + } - foreach (var fieldInfo in dtoInfo.Fields) - { - SwaggerSchema propertyObject = GetTypeSchema(serviceInfo.GetFieldType(fieldInfo)!); - if (propertyObject.Ref == null) - { - propertyObject.Description = GetSummaryOrNull(fieldInfo); - propertyObject.Obsolete = GetObsoleteOrNull(fieldInfo); - - if (fieldInfo.IsRequired) - (requiredFieldNames ??= new List()).Add(fieldInfo.Name); - } - propertiesObject[fieldInfo.Name] = propertyObject; - } + private static SwaggerSchema GetDtoSchema(ServiceInfo serviceInfo, ServiceDtoInfo dtoInfo) + { + var propertiesObject = new OurDictionary(); + List? requiredFieldNames = null; - return new SwaggerSchema + foreach (var fieldInfo in dtoInfo.Fields) + { + SwaggerSchema propertyObject = GetTypeSchema(serviceInfo.GetFieldType(fieldInfo)!); + if (propertyObject.Ref == null) { - Type = SwaggerSchemaType.Object, - Description = GetSummaryOrNull(dtoInfo), - Properties = propertiesObject, - Required = requiredFieldNames, - Obsolete = GetObsoleteOrNull(dtoInfo), - Remarks = GetRemarksOrNull(dtoInfo), - }; + propertyObject.Description = GetSummaryOrNull(fieldInfo); + propertyObject.Obsolete = GetObsoleteOrNull(fieldInfo); + + if (fieldInfo.IsRequired) + (requiredFieldNames ??= new List()).Add(fieldInfo.Name); + } + propertiesObject[fieldInfo.Name] = propertyObject; } - private static T GetDtoSchemaRef(ServiceDtoInfo dtoInfo) - where T : ISwaggerSchema, new() + return new SwaggerSchema { - return new T - { - Ref = "#/definitions/" + dtoInfo.Name, - }; - } + Type = SwaggerSchemaType.Object, + Description = GetSummaryOrNull(dtoInfo), + Properties = propertiesObject, + Required = requiredFieldNames, + Obsolete = GetObsoleteOrNull(dtoInfo), + Remarks = GetRemarksOrNull(dtoInfo), + }; + } - private static T GetEnumSchema(ServiceEnumInfo enumInfo) - where T : ISwaggerSchema, new() + private static T GetDtoSchemaRef(ServiceDtoInfo dtoInfo) + where T : ISwaggerSchema, new() + { + return new T { - return new T - { - Type = SwaggerSchemaType.String, - Enum = enumInfo.Values.Select(x => (JToken) x.Name).ToList(), - }; - } + Ref = "#/definitions/" + dtoInfo.Name, + }; + } - private static T GetErrorSchemaRef() - where T : ISwaggerSchema, new() + private static T GetEnumSchema(ServiceEnumInfo enumInfo) + where T : ISwaggerSchema, new() + { + return new T { - return new T - { - Ref = "#/definitions/Error", - }; - } + Type = SwaggerSchemaType.String, + Enum = enumInfo.Values.Select(x => (JToken) x.Name).ToList(), + }; + } - private static T GetResultTypeRef(ServiceTypeInfo type) - where T : ISwaggerSchema, new() + private static T GetErrorSchemaRef() + where T : ISwaggerSchema, new() + { + return new T { - return new T - { - Ref = "#/definitions/" + GetTypeAsDtoName(type), - }; - } + Ref = "#/definitions/Error", + }; + } - private static T GetArrayOfSchema(ServiceTypeInfo type) - where T : ISwaggerSchema, new() + private static T GetResultTypeRef(ServiceTypeInfo type) + where T : ISwaggerSchema, new() + { + return new T { - return new T - { - Type = SwaggerSchemaType.Array, - Items = GetTypeSchema(type), - }; - } + Ref = "#/definitions/" + GetTypeAsDtoName(type), + }; + } - private static SwaggerSchema GetMapOfSchema(ServiceTypeInfo type) + private static T GetArrayOfSchema(ServiceTypeInfo type) + where T : ISwaggerSchema, new() + { + return new T { - return new SwaggerSchema - { - Type = SwaggerSchemaType.Object, - AdditionalProperties = GetTypeSchema(type), - }; - } + Type = SwaggerSchemaType.Array, + Items = GetTypeSchema(type), + }; + } - private static object? ConvertJTokenToObject(JToken token) + private static SwaggerSchema GetMapOfSchema(ServiceTypeInfo type) + { + return new SwaggerSchema { - if (token is JValue value) - return value.Value; + Type = SwaggerSchemaType.Object, + AdditionalProperties = GetTypeSchema(type), + }; + } - if (token is JArray) - return token.AsEnumerable().Select(ConvertJTokenToObject).ToList(); + private static object? ConvertJTokenToObject(JToken token) + { + if (token is JValue value) + return value.Value; - if (token is JObject) - { - var dictionary = new OurDictionary(); - foreach (var property in token.AsEnumerable().Cast()) - dictionary[property.Name] = ConvertJTokenToObject(property.Value); - return dictionary; - } + if (token is JArray) + return token.AsEnumerable().Select(ConvertJTokenToObject).ToList(); - throw new InvalidOperationException("Unexpected token: " + token); + if (token is JObject) + { + var dictionary = new OurDictionary(); + foreach (var property in token.AsEnumerable().Cast()) + dictionary[property.Name] = ConvertJTokenToObject(property.Value); + return dictionary; } - private sealed class OurEventEmitter : ChainedEventEmitter + throw new InvalidOperationException("Unexpected token: " + token); + } + + private sealed class OurEventEmitter : ChainedEventEmitter + { + public OurEventEmitter(IEventEmitter nextEmitter) + : base(nextEmitter) { - public OurEventEmitter(IEventEmitter nextEmitter) - : base(nextEmitter) - { - } + } - public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter) - { - // prefer the literal style for multi-line strings - if (eventInfo.Source.Type == typeof(string) && eventInfo.Style == ScalarStyle.Any && ((string) eventInfo.Source.Value!).IndexOf('\n') != -1) - eventInfo.Style = ScalarStyle.Literal; + public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter) + { + // prefer the literal style for multi-line strings + if (eventInfo.Source.Type == typeof(string) && eventInfo.Style == ScalarStyle.Any && ((string) eventInfo.Source.Value!).IndexOf('\n') != -1) + eventInfo.Style = ScalarStyle.Literal; - // ensure strings that look like numbers remain strings - double unused; - if (eventInfo.Source.Type == typeof(string) && eventInfo.Style == ScalarStyle.Any && double.TryParse((string) eventInfo.Source.Value!, out unused)) - eventInfo.Style = ScalarStyle.SingleQuoted; + // ensure strings that look like numbers remain strings + if (eventInfo.Source.Type == typeof(string) && eventInfo.Style == ScalarStyle.Any && double.TryParse((string) eventInfo.Source.Value!, out _)) + eventInfo.Style = ScalarStyle.SingleQuoted; - base.Emit(eventInfo, emitter); - } + base.Emit(eventInfo, emitter); } + } - private sealed class OurDictionary : IDictionary, IReadOnlyDictionary + private sealed class OurDictionary : IDictionary, IReadOnlyDictionary + { + public OurDictionary() { - public OurDictionary() - { - m_dictionary = new Dictionary(); - m_keys = new List(); - } + m_dictionary = new Dictionary(); + m_keys = new List(); + } - public IEnumerator> GetEnumerator() - { - foreach (var key in m_keys) - yield return new KeyValuePair(key, m_dictionary[key]); - } + public IEnumerator> GetEnumerator() + { + foreach (var key in m_keys) + yield return new KeyValuePair(key, m_dictionary[key]); + } - public void Add(KeyValuePair item) => Add(item.Key, item.Value); + public void Add(KeyValuePair item) => Add(item.Key, item.Value); - public void Clear() - { - m_dictionary.Clear(); - m_keys.Clear(); - } + public void Clear() + { + m_dictionary.Clear(); + m_keys.Clear(); + } - public bool Contains(KeyValuePair item) => m_dictionary.Contains(item); + public bool Contains(KeyValuePair item) => m_dictionary.Contains(item); - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - throw new NotImplementedException(); - } + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } - public bool Remove(KeyValuePair item) - { - if (!((ICollection>) m_dictionary).Remove(item)) - return false; - m_keys.Remove(item.Key); - return true; - } + public bool Remove(KeyValuePair item) + { + if (!((ICollection>) m_dictionary).Remove(item)) + return false; + m_keys.Remove(item.Key); + return true; + } - public int Count => m_dictionary.Count; + public int Count => m_dictionary.Count; - public bool IsReadOnly => false; + public bool IsReadOnly => false; - public void Add(TKey key, TValue value) - { - m_dictionary.Add(key, value); - m_keys.Add(key); - } + public void Add(TKey key, TValue value) + { + m_dictionary.Add(key, value); + m_keys.Add(key); + } - public bool ContainsKey(TKey key) => m_dictionary.ContainsKey(key); + public bool ContainsKey(TKey key) => m_dictionary.ContainsKey(key); - public bool Remove(TKey key) - { - if (!m_dictionary.Remove(key)) - return false; - m_keys.Remove(key); - return true; - } + public bool Remove(TKey key) + { + if (!m_dictionary.Remove(key)) + return false; + m_keys.Remove(key); + return true; + } - public bool TryGetValue(TKey key, out TValue value) => m_dictionary.TryGetValue(key, out value); + public bool TryGetValue(TKey key, out TValue value) => m_dictionary.TryGetValue(key, out value); - public TValue this[TKey key] + public TValue this[TKey key] + { + get => m_dictionary[key]; + set { - get => m_dictionary[key]; - set - { - var replace = m_dictionary.ContainsKey(key); - m_dictionary[key] = value; - if (!replace) - m_keys.Add(key); - } + var replace = m_dictionary.ContainsKey(key); + m_dictionary[key] = value; + if (!replace) + m_keys.Add(key); } + } - public ICollection Keys => m_keys.ToList(); + public ICollection Keys => m_keys.ToList(); - public ICollection Values => m_keys.Select(x => m_dictionary[x]).ToList(); + public ICollection Values => m_keys.Select(x => m_dictionary[x]).ToList(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerable IReadOnlyDictionary.Keys => Keys; + IEnumerable IReadOnlyDictionary.Keys => Keys; - IEnumerable IReadOnlyDictionary.Values => Values; + IEnumerable IReadOnlyDictionary.Values => Values; - private readonly Dictionary m_dictionary; - private readonly List m_keys; - } + private readonly Dictionary m_dictionary; + private readonly List m_keys; } } diff --git a/src/Facility.Definition.Swagger/SwaggerGeneratorSettings.cs b/src/Facility.Definition.Swagger/SwaggerGeneratorSettings.cs index 2cc47e2..577d323 100644 --- a/src/Facility.Definition.Swagger/SwaggerGeneratorSettings.cs +++ b/src/Facility.Definition.Swagger/SwaggerGeneratorSettings.cs @@ -1,25 +1,24 @@ using Facility.Definition.CodeGen; -namespace Facility.Definition.Swagger +namespace Facility.Definition.Swagger; + +/// +/// Settings for generating Swagger. +/// +public sealed class SwaggerGeneratorSettings : FileGeneratorSettings { /// - /// Settings for generating Swagger. + /// Generates a Facility Service Definition (instead of Swagger). /// - public sealed class SwaggerGeneratorSettings : FileGeneratorSettings - { - /// - /// Generates a Facility Service Definition (instead of Swagger). - /// - public bool GeneratesFsd { get; set; } + public bool GeneratesFsd { get; set; } - /// - /// Generates JSON (instead of YAML). - /// - public bool GeneratesJson { get; set; } + /// + /// Generates JSON (instead of YAML). + /// + public bool GeneratesJson { get; set; } - /// - /// Overrides the service name. - /// - public string? ServiceName { get; set; } - } + /// + /// Overrides the service name. + /// + public string? ServiceName { get; set; } } diff --git a/src/Facility.Definition.Swagger/SwaggerInfo.cs b/src/Facility.Definition.Swagger/SwaggerInfo.cs index 6be10c5..6491e29 100644 --- a/src/Facility.Definition.Swagger/SwaggerInfo.cs +++ b/src/Facility.Definition.Swagger/SwaggerInfo.cs @@ -1,30 +1,27 @@ using Newtonsoft.Json; using YamlDotNet.Serialization; -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public class SwaggerInfo { - public class SwaggerInfo - { - public string? Title { get; set; } + public string? Title { get; set; } - public string? Description { get; set; } + public string? Description { get; set; } - public string? TermsOfService { get; set; } + public string? TermsOfService { get; set; } - public SwaggerContact? Contact { get; set; } + public SwaggerContact? Contact { get; set; } - public SwaggerLicense? License { get; set; } + public SwaggerLicense? License { get; set; } - public string? Version { get; set; } + public string? Version { get; set; } - [JsonProperty("x-identifier")] - [YamlMember(Alias = "x-identifier")] - public string? Identifier { get; set; } + [JsonProperty("x-identifier")] + [YamlMember(Alias = "x-identifier")] + public string? Identifier { get; set; } - [JsonProperty("x-codegen")] - [YamlMember(Alias = "x-codegen")] - public string? CodeGen { get; set; } - } + [JsonProperty("x-codegen")] + [YamlMember(Alias = "x-codegen")] + public string? CodeGen { get; set; } } diff --git a/src/Facility.Definition.Swagger/SwaggerLicense.cs b/src/Facility.Definition.Swagger/SwaggerLicense.cs index 0b6e782..0a147e8 100644 --- a/src/Facility.Definition.Swagger/SwaggerLicense.cs +++ b/src/Facility.Definition.Swagger/SwaggerLicense.cs @@ -1,11 +1,8 @@ -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public class SwaggerLicense { - public class SwaggerLicense - { - public string? Name { get; set; } + public string? Name { get; set; } - public string? Url { get; set; } - } + public string? Url { get; set; } } diff --git a/src/Facility.Definition.Swagger/SwaggerOperation.cs b/src/Facility.Definition.Swagger/SwaggerOperation.cs index 25a5b57..cf543b9 100644 --- a/src/Facility.Definition.Swagger/SwaggerOperation.cs +++ b/src/Facility.Definition.Swagger/SwaggerOperation.cs @@ -1,33 +1,28 @@ -using System.Collections.Generic; +namespace Facility.Definition.Swagger; -#pragma warning disable 1591 - -namespace Facility.Definition.Swagger +public class SwaggerOperation { - public class SwaggerOperation - { - public IList? Tags { get; set; } + public IList? Tags { get; set; } - public string? Summary { get; set; } + public string? Summary { get; set; } - public string? Description { get; set; } + public string? Description { get; set; } - public SwaggerExternalDocumentation? ExternalDocs { get; set; } + public SwaggerExternalDocumentation? ExternalDocs { get; set; } - public string? OperationId { get; set; } + public string? OperationId { get; set; } - public IList? Consumes { get; set; } + public IList? Consumes { get; set; } - public IList? Produces { get; set; } + public IList? Produces { get; set; } - public IList? Parameters { get; set; } + public IList? Parameters { get; set; } - public IDictionary? Responses { get; set; } + public IDictionary? Responses { get; set; } - public IList? Schemes { get; set; } + public IList? Schemes { get; set; } - public bool? Deprecated { get; set; } + public bool? Deprecated { get; set; } - public IList>>? Security { get; set; } - } + public IList>>? Security { get; set; } } diff --git a/src/Facility.Definition.Swagger/SwaggerOperations.cs b/src/Facility.Definition.Swagger/SwaggerOperations.cs index 7b46743..5fff886 100644 --- a/src/Facility.Definition.Swagger/SwaggerOperations.cs +++ b/src/Facility.Definition.Swagger/SwaggerOperations.cs @@ -1,31 +1,27 @@ -using System.Collections.Generic; using Newtonsoft.Json; using YamlDotNet.Serialization; -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public class SwaggerOperations { - public class SwaggerOperations - { - [JsonProperty("$ref")] - [YamlMember(Alias = "$ref")] - public string? Ref { get; set; } + [JsonProperty("$ref")] + [YamlMember(Alias = "$ref")] + public string? Ref { get; set; } - public SwaggerOperation? Get { get; set; } + public SwaggerOperation? Get { get; set; } - public SwaggerOperation? Post { get; set; } + public SwaggerOperation? Post { get; set; } - public SwaggerOperation? Put { get; set; } + public SwaggerOperation? Put { get; set; } - public SwaggerOperation? Delete { get; set; } + public SwaggerOperation? Delete { get; set; } - public SwaggerOperation? Options { get; set; } + public SwaggerOperation? Options { get; set; } - public SwaggerOperation? Head { get; set; } + public SwaggerOperation? Head { get; set; } - public SwaggerOperation? Patch { get; set; } + public SwaggerOperation? Patch { get; set; } - public IList? Parameters { get; set; } - } + public IList? Parameters { get; set; } } diff --git a/src/Facility.Definition.Swagger/SwaggerParameter.cs b/src/Facility.Definition.Swagger/SwaggerParameter.cs index 9dd6578..1670851 100644 --- a/src/Facility.Definition.Swagger/SwaggerParameter.cs +++ b/src/Facility.Definition.Swagger/SwaggerParameter.cs @@ -1,70 +1,66 @@ -using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using YamlDotNet.Serialization; -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public class SwaggerParameter : ISwaggerSchema { - public class SwaggerParameter : ISwaggerSchema - { - [JsonProperty("$ref")] - [YamlMember(Alias = "$ref")] - public string? Ref { get; set; } // parameters, schema + [JsonProperty("$ref")] + [YamlMember(Alias = "$ref")] + public string? Ref { get; set; } // parameters, schema - public string? In { get; set; } // parameters + public string? In { get; set; } // parameters - public string? Name { get; set; } // parameters + public string? Name { get; set; } // parameters - public string? Description { get; set; } // parameters, headers, schema + public string? Description { get; set; } // parameters, headers, schema - public bool? Required { get; set; } // parameters + public bool? Required { get; set; } // parameters - public SwaggerSchema? Schema { get; set; } // parameters (body) + public SwaggerSchema? Schema { get; set; } // parameters (body) - public string? Type { get; set; } // parameters (non-body), headers, schema + public string? Type { get; set; } // parameters (non-body), headers, schema - public string? Format { get; set; } // parameters (non-body), headers, schema + public string? Format { get; set; } // parameters (non-body), headers, schema - public bool? AllowEmptyValue { get; set; } // parameters (non-body) + public bool? AllowEmptyValue { get; set; } // parameters (non-body) - public SwaggerSchema? Items { get; set; } // parameters (non-body), headers, schema + public SwaggerSchema? Items { get; set; } // parameters (non-body), headers, schema - public string? CollectionFormat { get; set; } // parameters (non-body), headers + public string? CollectionFormat { get; set; } // parameters (non-body), headers - public JToken? Default { get; set; } // parameters (non-body), headers, schema + public JToken? Default { get; set; } // parameters (non-body), headers, schema - public double? Maximum { get; set; } // parameters (non-body), headers, schema + public double? Maximum { get; set; } // parameters (non-body), headers, schema - public bool? ExclusiveMaximum { get; set; } // parameters (non-body), headers, schema + public bool? ExclusiveMaximum { get; set; } // parameters (non-body), headers, schema - public double? Minimum { get; set; } // parameters (non-body), headers, schema + public double? Minimum { get; set; } // parameters (non-body), headers, schema - public bool? ExclusiveMinimum { get; set; } // parameters (non-body), headers, schema + public bool? ExclusiveMinimum { get; set; } // parameters (non-body), headers, schema - public int? MaxLength { get; set; } // parameters (non-body), headers, schema + public int? MaxLength { get; set; } // parameters (non-body), headers, schema - public int? MinLength { get; set; } // parameters (non-body), headers, schema + public int? MinLength { get; set; } // parameters (non-body), headers, schema - public string? Pattern { get; set; } // parameters (non-body), headers, schema + public string? Pattern { get; set; } // parameters (non-body), headers, schema - public int? MaxItems { get; set; } // parameters (non-body), headers, schema + public int? MaxItems { get; set; } // parameters (non-body), headers, schema - public int? MinItems { get; set; } // parameters (non-body), headers, schema + public int? MinItems { get; set; } // parameters (non-body), headers, schema - public bool? UniqueItems { get; set; } // parameters (non-body), headers, schema + public bool? UniqueItems { get; set; } // parameters (non-body), headers, schema - public IList? Enum { get; set; } // parameters (non-body), headers, schema + public IList? Enum { get; set; } // parameters (non-body), headers, schema - public double? MultipleOf { get; set; } // parameters (non-body), headers, schema + public double? MultipleOf { get; set; } // parameters (non-body), headers, schema - [JsonProperty("x-identifier")] - [YamlMember(Alias = "x-identifier")] - public string? Identifier { get; set; } // parameters, headers, schema + [JsonProperty("x-identifier")] + [YamlMember(Alias = "x-identifier")] + public string? Identifier { get; set; } // parameters, headers, schema - [JsonProperty("x-obsolete")] - [YamlMember(Alias = "x-obsolete")] - public bool? Obsolete { get; set; } // parameters, headers, schema - } + [JsonProperty("x-obsolete")] + [YamlMember(Alias = "x-obsolete")] + public bool? Obsolete { get; set; } // parameters, headers, schema } diff --git a/src/Facility.Definition.Swagger/SwaggerParameterKind.cs b/src/Facility.Definition.Swagger/SwaggerParameterKind.cs index 68e4de1..07d2a14 100644 --- a/src/Facility.Definition.Swagger/SwaggerParameterKind.cs +++ b/src/Facility.Definition.Swagger/SwaggerParameterKind.cs @@ -1,13 +1,10 @@ -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public static class SwaggerParameterKind { - public static class SwaggerParameterKind - { - public const string Body = "body"; - public const string Query = "query"; - public const string Path = "path"; - public const string Header = "header"; - public const string FormData = "formData"; - } + public const string Body = "body"; + public const string Query = "query"; + public const string Path = "path"; + public const string Header = "header"; + public const string FormData = "formData"; } diff --git a/src/Facility.Definition.Swagger/SwaggerParser.cs b/src/Facility.Definition.Swagger/SwaggerParser.cs index 6618ca0..e75f00e 100644 --- a/src/Facility.Definition.Swagger/SwaggerParser.cs +++ b/src/Facility.Definition.Swagger/SwaggerParser.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; using System.Text.RegularExpressions; using Facility.Definition.CodeGen; using Facility.Definition.Fsd; @@ -8,131 +5,130 @@ using YamlDotNet.Core; using YamlDotNet.Serialization; -namespace Facility.Definition.Swagger +namespace Facility.Definition.Swagger; + +/// +/// Parses Swagger (OpenAPI) 2.0. +/// +public sealed class SwaggerParser : ServiceParser { /// - /// Parses Swagger (OpenAPI) 2.0. + /// The service name (defaults to 'info/x-identifier' or 'info/title'). + /// + public string? ServiceName { get; set; } + + /// + /// Implements TryParseDefinition. /// - public sealed class SwaggerParser : ServiceParser + protected override bool TryParseDefinitionCore(ServiceDefinitionText text, out ServiceInfo? service, out IReadOnlyList errors) { - /// - /// The service name (defaults to 'info/x-identifier' or 'info/title'). - /// - public string? ServiceName { get; set; } - - /// - /// Implements TryParseDefinition. - /// - protected override bool TryParseDefinitionCore(ServiceDefinitionText text, out ServiceInfo? service, out IReadOnlyList errors) - { - var isFsd = new FsdParser().TryParseDefinition(text, out service, out errors); - if (isFsd || text.Name.EndsWith(".fsd", StringComparison.OrdinalIgnoreCase)) - return isFsd; + var isFsd = new FsdParser().TryParseDefinition(text, out service, out errors); + if (isFsd || text.Name.EndsWith(".fsd", StringComparison.OrdinalIgnoreCase)) + return isFsd; - service = null; + service = null; - if (string.IsNullOrWhiteSpace(text.Text)) - { - errors = new[] { new ServiceDefinitionError("Service definition is missing.", new ServiceDefinitionPosition(text.Name, 1, 1)) }; - return false; - } + if (string.IsNullOrWhiteSpace(text.Text)) + { + errors = new[] { new ServiceDefinitionError("Service definition is missing.", new ServiceDefinitionPosition(text.Name, 1, 1)) }; + return false; + } - SwaggerService swaggerService; - SwaggerParserContext context; + SwaggerService swaggerService; + SwaggerParserContext context; - if (!s_detectJsonRegex.IsMatch(text.Text)) + if (!s_detectJsonRegex.IsMatch(text.Text)) + { + // parse YAML + var yamlDeserializer = new DeserializerBuilder() + .IgnoreUnmatchedProperties() + .WithNamingConvention(new OurNamingConvention()) + .Build(); + using (var stringReader = new StringReader(text.Text)) { - // parse YAML - var yamlDeserializer = new DeserializerBuilder() - .IgnoreUnmatchedProperties() - .WithNamingConvention(new OurNamingConvention()) - .Build(); - using (var stringReader = new StringReader(text.Text)) + try { - try - { - swaggerService = yamlDeserializer.Deserialize(stringReader); - } - catch (YamlException exception) - { - var errorMessage = exception.InnerException?.Message ?? exception.Message; - const string errorStart = "): "; - var errorStartIndex = errorMessage.IndexOf(errorStart, StringComparison.OrdinalIgnoreCase); - if (errorStartIndex != -1) - errorMessage = errorMessage.Substring(errorStartIndex + errorStart.Length); - - errors = new[] { new ServiceDefinitionError(errorMessage, new ServiceDefinitionPosition(text.Name, exception.End.Line, exception.End.Column)) }; - return false; - } + swaggerService = yamlDeserializer.Deserialize(stringReader); } - - if (swaggerService == null) + catch (YamlException exception) { - errors = new[] { new ServiceDefinitionError("Service definition is missing.", new ServiceDefinitionPosition(text.Name, 1, 1)) }; + var errorMessage = exception.InnerException?.Message ?? exception.Message; + const string errorStart = "): "; + var errorStartIndex = errorMessage.IndexOf(errorStart, StringComparison.OrdinalIgnoreCase); + if (errorStartIndex != -1) + errorMessage = errorMessage.Substring(errorStartIndex + errorStart.Length); + + errors = new[] { new ServiceDefinitionError(errorMessage, new ServiceDefinitionPosition(text.Name, exception.End.Line, exception.End.Column)) }; return false; } + } - context = SwaggerParserContext.FromYaml(text); + if (swaggerService == null) + { + errors = new[] { new ServiceDefinitionError("Service definition is missing.", new ServiceDefinitionPosition(text.Name, 1, 1)) }; + return false; } - else + + context = SwaggerParserContext.FromYaml(text); + } + else + { + // parse JSON + using (var stringReader = new StringReader(text.Text)) + using (var jsonTextReader = new JsonTextReader(stringReader)) { - // parse JSON - using (var stringReader = new StringReader(text.Text)) - using (var jsonTextReader = new JsonTextReader(stringReader)) + try { - try - { - swaggerService = JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings).Deserialize(jsonTextReader)!; - } - catch (JsonException exception) - { - errors = new[] { new ServiceDefinitionError(exception.Message, new ServiceDefinitionPosition(text.Name, jsonTextReader.LineNumber, jsonTextReader.LinePosition)) }; - return false; - } - - context = SwaggerParserContext.FromJson(text); + swaggerService = JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings).Deserialize(jsonTextReader)!; + } + catch (JsonException exception) + { + errors = new[] { new ServiceDefinitionError(exception.Message, new ServiceDefinitionPosition(text.Name, jsonTextReader.LineNumber, jsonTextReader.LinePosition)) }; + return false; } - } - var conversion = SwaggerConversion.Create(swaggerService, ServiceName, context); - service = conversion.Service; - errors = conversion.Errors; - return errors.Count == 0; + context = SwaggerParserContext.FromJson(text); + } } - /// - /// Converts Swagger (OpenAPI) 2.0 into a service definition. - /// - /// Thrown if the service would be invalid. - public ServiceInfo ConvertSwaggerService(SwaggerService swaggerService) - { - if (TryConvertSwaggerService(swaggerService, out var service, out var errors)) - return service!; - else - throw new ServiceDefinitionException(errors); - } + var conversion = SwaggerConversion.Create(swaggerService, ServiceName, context); + service = conversion.Service; + errors = conversion.Errors; + return errors.Count == 0; + } - /// - /// Attempts to convert Swagger (OpenAPI) 2.0 into a service definition. - /// - public bool TryConvertSwaggerService(SwaggerService swaggerService, out ServiceInfo? service, out IReadOnlyList errors) - { - var conversion = SwaggerConversion.Create(swaggerService, ServiceName, SwaggerParserContext.None); - service = conversion.Service; - errors = conversion.Errors; - return errors.Count == 0; - } + /// + /// Converts Swagger (OpenAPI) 2.0 into a service definition. + /// + /// Thrown if the service would be invalid. + public ServiceInfo ConvertSwaggerService(SwaggerService swaggerService) + { + if (TryConvertSwaggerService(swaggerService, out var service, out var errors)) + return service!; + else + throw new ServiceDefinitionException(errors); + } + + /// + /// Attempts to convert Swagger (OpenAPI) 2.0 into a service definition. + /// + public bool TryConvertSwaggerService(SwaggerService swaggerService, out ServiceInfo? service, out IReadOnlyList errors) + { + var conversion = SwaggerConversion.Create(swaggerService, ServiceName, SwaggerParserContext.None); + service = conversion.Service; + errors = conversion.Errors; + return errors.Count == 0; + } - private sealed class OurNamingConvention : INamingConvention + private sealed class OurNamingConvention : INamingConvention + { + public string Apply(string value) { - public string Apply(string value) - { - if (value[0] >= 'A' && value[0] <= 'Z') - value = CodeGenUtility.ToCamelCase(value); - return value; - } + if (value[0] >= 'A' && value[0] <= 'Z') + value = CodeGenUtility.ToCamelCase(value); + return value; } - - private static readonly Regex s_detectJsonRegex = new Regex(@"^\s*[{/]", RegexOptions.Singleline); } + + private static readonly Regex s_detectJsonRegex = new Regex(@"^\s*[{/]", RegexOptions.Singleline); } diff --git a/src/Facility.Definition.Swagger/SwaggerParserContext.cs b/src/Facility.Definition.Swagger/SwaggerParserContext.cs index f14d592..d234f18 100644 --- a/src/Facility.Definition.Swagger/SwaggerParserContext.cs +++ b/src/Facility.Definition.Swagger/SwaggerParserContext.cs @@ -1,90 +1,88 @@ -using System.IO; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using YamlDotNet.RepresentationModel; -namespace Facility.Definition.Swagger +namespace Facility.Definition.Swagger; + +internal sealed class SwaggerParserContext { - internal sealed class SwaggerParserContext - { - public static SwaggerParserContext None => new SwaggerParserContext(serviceDefinitionText: null, isYaml: false); + public static SwaggerParserContext None => new SwaggerParserContext(serviceDefinitionText: null, isYaml: false); - public static SwaggerParserContext FromYaml(ServiceDefinitionText serviceDefinitionText) => new SwaggerParserContext(serviceDefinitionText, isYaml: true); + public static SwaggerParserContext FromYaml(ServiceDefinitionText serviceDefinitionText) => new SwaggerParserContext(serviceDefinitionText, isYaml: true); - public static SwaggerParserContext FromJson(ServiceDefinitionText serviceDefinitionText) => new SwaggerParserContext(serviceDefinitionText, isYaml: false); + public static SwaggerParserContext FromJson(ServiceDefinitionText serviceDefinitionText) => new SwaggerParserContext(serviceDefinitionText, isYaml: false); - public SwaggerParserContext Root => new SwaggerParserContext(m_serviceDefinitionText, m_isYaml); + public SwaggerParserContext Root => new SwaggerParserContext(m_serviceDefinitionText, m_isYaml); - public ServiceDefinitionPosition? CreatePosition(string? path = null) => m_serviceDefinitionText == null ? null : new ServiceDefinitionPosition(m_serviceDefinitionText.Name, () => FindLineColumn(path)); + public ServiceDefinitionPosition? CreatePosition(string? path = null) => m_serviceDefinitionText == null ? null : new ServiceDefinitionPosition(m_serviceDefinitionText.Name, () => FindLineColumn(path)); - public ServicePart? CreatePart(string? path = null) => m_serviceDefinitionText == null ? null : new ServicePart(ServicePartKind.Keyword, CreatePosition(path)!); + public ServicePart? CreatePart(string? path = null) => m_serviceDefinitionText == null ? null : new ServicePart(ServicePartKind.Keyword, CreatePosition(path)!); - public ServiceDefinitionError CreateError(string error, string? path = null) => new ServiceDefinitionError(error, CreatePosition(path)); + public ServiceDefinitionError CreateError(string error, string? path = null) => new ServiceDefinitionError(error, CreatePosition(path)); - public SwaggerParserContext CreateContext(string path) => new SwaggerParserContext(m_serviceDefinitionText, m_isYaml, ResolvePath(path)); + public SwaggerParserContext CreateContext(string path) => new SwaggerParserContext(m_serviceDefinitionText, m_isYaml, ResolvePath(path)); - private SwaggerParserContext(ServiceDefinitionText? serviceDefinitionText, bool isYaml, string? path = null) - { - m_serviceDefinitionText = serviceDefinitionText; - m_isYaml = isYaml; - m_path = path; - } + private SwaggerParserContext(ServiceDefinitionText? serviceDefinitionText, bool isYaml, string? path = null) + { + m_serviceDefinitionText = serviceDefinitionText; + m_isYaml = isYaml; + m_path = path; + } - private (int LineNumber, int ColumnNumber) FindLineColumn(string? path) + private (int LineNumber, int ColumnNumber) FindLineColumn(string? path) + { + if (m_isYaml) { - if (m_isYaml) - { - var yamlStream = new YamlStream(); - using (var stringReader = new StringReader(m_serviceDefinitionText!.Text)) - yamlStream.Load(stringReader); + var yamlStream = new YamlStream(); + using (var stringReader = new StringReader(m_serviceDefinitionText!.Text)) + yamlStream.Load(stringReader); - var node = yamlStream.Documents[0].RootNode; - if (path != null && path.Length != 0) + var node = yamlStream.Documents[0].RootNode; + if (path != null && path.Length != 0) + { + foreach (var part in path.Split('/')) { - foreach (var part in path.Split('/')) - { - if (!(node is YamlMappingNode mappingNode)) - break; + if (!(node is YamlMappingNode mappingNode)) + break; - mappingNode.Children.TryGetValue(new YamlScalarNode(part), out var childNode); - if (childNode == null) - break; + mappingNode.Children.TryGetValue(new YamlScalarNode(part), out var childNode); + if (childNode == null) + break; - node = childNode; - } + node = childNode; } - - return (node.End.Line, node.End.Column); } - else - { - JToken token = JToken.Parse(m_serviceDefinitionText!.Text); - if (path != null && path.Length != 0) + return (node.End.Line, node.End.Column); + } + else + { + JToken token = JToken.Parse(m_serviceDefinitionText!.Text); + + if (path != null && path.Length != 0) + { + foreach (var part in path.Split('/')) { - foreach (var part in path.Split('/')) - { - if (!(token is JObject jObject)) - break; + if (!(token is JObject jObject)) + break; - var childToken = jObject[part]; - if (childToken == null) - break; + var childToken = jObject[part]; + if (childToken == null) + break; - token = childToken; - } + token = childToken; } - JToken pathToken = string.IsNullOrEmpty(path) ? token : token.SelectToken(ResolvePath(path)!)!; - - var lineInfo = (IJsonLineInfo) (pathToken ?? token); - return (lineInfo.LineNumber, lineInfo.LinePosition); } + JToken pathToken = string.IsNullOrEmpty(path) ? token : token.SelectToken(ResolvePath(path)!)!; + + var lineInfo = (IJsonLineInfo) (pathToken ?? token); + return (lineInfo.LineNumber, lineInfo.LinePosition); } + } - private string? ResolvePath(string? path) => string.IsNullOrEmpty(path) ? m_path : string.IsNullOrEmpty(m_path) ? path : m_path + "." + path; + private string? ResolvePath(string? path) => string.IsNullOrEmpty(path) ? m_path : string.IsNullOrEmpty(m_path) ? path : m_path + "." + path; - private readonly ServiceDefinitionText? m_serviceDefinitionText; - private readonly bool m_isYaml; - private readonly string? m_path; - } + private readonly ServiceDefinitionText? m_serviceDefinitionText; + private readonly bool m_isYaml; + private readonly string? m_path; } diff --git a/src/Facility.Definition.Swagger/SwaggerResponse.cs b/src/Facility.Definition.Swagger/SwaggerResponse.cs index ead51dc..9ca41a1 100644 --- a/src/Facility.Definition.Swagger/SwaggerResponse.cs +++ b/src/Facility.Definition.Swagger/SwaggerResponse.cs @@ -1,28 +1,24 @@ -using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using YamlDotNet.Serialization; -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public class SwaggerResponse { - public class SwaggerResponse - { - [JsonProperty("$ref")] - [YamlMember(Alias = "$ref")] - public string? Ref { get; set; } + [JsonProperty("$ref")] + [YamlMember(Alias = "$ref")] + public string? Ref { get; set; } - public string? Description { get; set; } + public string? Description { get; set; } - public SwaggerSchema? Schema { get; set; } + public SwaggerSchema? Schema { get; set; } - public IDictionary? Headers { get; set; } + public IDictionary? Headers { get; set; } - public JObject? Examples { get; set; } + public JObject? Examples { get; set; } - [JsonProperty("x-identifier")] - [YamlMember(Alias = "x-identifier")] - public string? Identifier { get; set; } - } + [JsonProperty("x-identifier")] + [YamlMember(Alias = "x-identifier")] + public string? Identifier { get; set; } } diff --git a/src/Facility.Definition.Swagger/SwaggerSchema.cs b/src/Facility.Definition.Swagger/SwaggerSchema.cs index 186a66c..1ddd6d1 100644 --- a/src/Facility.Definition.Swagger/SwaggerSchema.cs +++ b/src/Facility.Definition.Swagger/SwaggerSchema.cs @@ -1,86 +1,82 @@ -using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using YamlDotNet.Serialization; -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public class SwaggerSchema : ISwaggerSchema { - public class SwaggerSchema : ISwaggerSchema - { - [JsonProperty("$ref")] - [YamlMember(Alias = "$ref")] - public string? Ref { get; set; } // parameters, schema + [JsonProperty("$ref")] + [YamlMember(Alias = "$ref")] + public string? Ref { get; set; } // parameters, schema - public string? Description { get; set; } // parameters, headers, schema + public string? Description { get; set; } // parameters, headers, schema - public IList? Required { get; set; } // schema + public IList? Required { get; set; } // schema - public string? Title { get; set; } // schema + public string? Title { get; set; } // schema - public string? Type { get; set; } // parameters (non-body), headers, schema + public string? Type { get; set; } // parameters (non-body), headers, schema - public string? Format { get; set; } // parameters (non-body), headers, schema + public string? Format { get; set; } // parameters (non-body), headers, schema - public SwaggerSchema? Items { get; set; } // parameters (non-body), headers, schema + public SwaggerSchema? Items { get; set; } // parameters (non-body), headers, schema - public int? MaxProperties { get; set; } // schema + public int? MaxProperties { get; set; } // schema - public int? MinProperties { get; set; } // schema + public int? MinProperties { get; set; } // schema - public JToken? Default { get; set; } // parameters (non-body), headers, schema + public JToken? Default { get; set; } // parameters (non-body), headers, schema - public double? Maximum { get; set; } // parameters (non-body), headers, schema + public double? Maximum { get; set; } // parameters (non-body), headers, schema - public bool? ExclusiveMaximum { get; set; } // parameters (non-body), headers, schema + public bool? ExclusiveMaximum { get; set; } // parameters (non-body), headers, schema - public double? Minimum { get; set; } // parameters (non-body), headers, schema + public double? Minimum { get; set; } // parameters (non-body), headers, schema - public bool? ExclusiveMinimum { get; set; } // parameters (non-body), headers, schema + public bool? ExclusiveMinimum { get; set; } // parameters (non-body), headers, schema - public int? MaxLength { get; set; } // parameters (non-body), headers, schema + public int? MaxLength { get; set; } // parameters (non-body), headers, schema - public int? MinLength { get; set; } // parameters (non-body), headers, schema + public int? MinLength { get; set; } // parameters (non-body), headers, schema - public string? Pattern { get; set; } // parameters (non-body), headers, schema + public string? Pattern { get; set; } // parameters (non-body), headers, schema - public int? MaxItems { get; set; } // parameters (non-body), headers, schema + public int? MaxItems { get; set; } // parameters (non-body), headers, schema - public int? MinItems { get; set; } // parameters (non-body), headers, schema + public int? MinItems { get; set; } // parameters (non-body), headers, schema - public bool? UniqueItems { get; set; } // parameters (non-body), headers, schema + public bool? UniqueItems { get; set; } // parameters (non-body), headers, schema - public IList? Enum { get; set; } // parameters (non-body), headers, schema + public IList? Enum { get; set; } // parameters (non-body), headers, schema - public double? MultipleOf { get; set; } // parameters (non-body), headers, schema + public double? MultipleOf { get; set; } // parameters (non-body), headers, schema - public IDictionary? Properties { get; set; } // schema + public IDictionary? Properties { get; set; } // schema - public IList? AllOf { get; set; } // schema + public IList? AllOf { get; set; } // schema - public SwaggerSchema? AdditionalProperties { get; set; } // schema + public SwaggerSchema? AdditionalProperties { get; set; } // schema - public string? Discriminator { get; set; } // schema + public string? Discriminator { get; set; } // schema - public bool? ReadOnly { get; set; } // schema + public bool? ReadOnly { get; set; } // schema - public JObject? Xml { get; set; } // schema + public JObject? Xml { get; set; } // schema - public SwaggerExternalDocumentation? ExternalDocs { get; set; } // schema + public SwaggerExternalDocumentation? ExternalDocs { get; set; } // schema - public JToken? Example { get; set; } // schema + public JToken? Example { get; set; } // schema - [JsonProperty("x-identifier")] - [YamlMember(Alias = "x-identifier")] - public string? Identifier { get; set; } // parameters, headers, schema + [JsonProperty("x-identifier")] + [YamlMember(Alias = "x-identifier")] + public string? Identifier { get; set; } // parameters, headers, schema - [JsonProperty("x-obsolete")] - [YamlMember(Alias = "x-obsolete")] - public bool? Obsolete { get; set; } // parameters, headers, schema + [JsonProperty("x-obsolete")] + [YamlMember(Alias = "x-obsolete")] + public bool? Obsolete { get; set; } // parameters, headers, schema - [JsonProperty("x-remarks")] - [YamlMember(Alias = "x-remarks")] - public string? Remarks { get; set; } // schema - } + [JsonProperty("x-remarks")] + [YamlMember(Alias = "x-remarks")] + public string? Remarks { get; set; } // schema } diff --git a/src/Facility.Definition.Swagger/SwaggerSchemaType.cs b/src/Facility.Definition.Swagger/SwaggerSchemaType.cs index c2f21fd..066ea6a 100644 --- a/src/Facility.Definition.Swagger/SwaggerSchemaType.cs +++ b/src/Facility.Definition.Swagger/SwaggerSchemaType.cs @@ -1,15 +1,12 @@ -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public static class SwaggerSchemaType { - public static class SwaggerSchemaType - { - public const string String = "string"; - public const string Number = "number"; - public const string Integer = "integer"; - public const string Boolean = "boolean"; - public const string Array = "array"; - public const string Object = "object"; - public const string File = "file"; - } + public const string String = "string"; + public const string Number = "number"; + public const string Integer = "integer"; + public const string Boolean = "boolean"; + public const string Array = "array"; + public const string Object = "object"; + public const string File = "file"; } diff --git a/src/Facility.Definition.Swagger/SwaggerSchemaTypeFormat.cs b/src/Facility.Definition.Swagger/SwaggerSchemaTypeFormat.cs index a0cbf85..8b41391 100644 --- a/src/Facility.Definition.Swagger/SwaggerSchemaTypeFormat.cs +++ b/src/Facility.Definition.Swagger/SwaggerSchemaTypeFormat.cs @@ -1,18 +1,15 @@ -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public static class SwaggerSchemaTypeFormat { - public static class SwaggerSchemaTypeFormat - { - public const string Int32 = "int32"; - public const string Int64 = "int64"; - public const string Float = "float"; - public const string Double = "double"; - public const string Decimal = "decimal"; - public const string Byte = "byte"; - public const string Binary = "binary"; - public const string Date = "date"; - public const string DateTime = "date-time"; - public const string Password = "password"; - } + public const string Int32 = "int32"; + public const string Int64 = "int64"; + public const string Float = "float"; + public const string Double = "double"; + public const string Decimal = "decimal"; + public const string Byte = "byte"; + public const string Binary = "binary"; + public const string Date = "date"; + public const string DateTime = "date-time"; + public const string Password = "password"; } diff --git a/src/Facility.Definition.Swagger/SwaggerScheme.cs b/src/Facility.Definition.Swagger/SwaggerScheme.cs index 3d6e9b3..8a75006 100644 --- a/src/Facility.Definition.Swagger/SwaggerScheme.cs +++ b/src/Facility.Definition.Swagger/SwaggerScheme.cs @@ -1,12 +1,9 @@ -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public static class SwaggerScheme { - public static class SwaggerScheme - { - public const string Http = "http"; - public const string Https = "https"; - public const string Ws = "ws"; - public const string Wss = "wss"; - } + public const string Http = "http"; + public const string Https = "https"; + public const string Ws = "ws"; + public const string Wss = "wss"; } diff --git a/src/Facility.Definition.Swagger/SwaggerSecurityScheme.cs b/src/Facility.Definition.Swagger/SwaggerSecurityScheme.cs index 51df941..ed4b13f 100644 --- a/src/Facility.Definition.Swagger/SwaggerSecurityScheme.cs +++ b/src/Facility.Definition.Swagger/SwaggerSecurityScheme.cs @@ -1,25 +1,20 @@ -using System.Collections.Generic; +namespace Facility.Definition.Swagger; -#pragma warning disable 1591 - -namespace Facility.Definition.Swagger +public class SwaggerSecurityScheme { - public class SwaggerSecurityScheme - { - public string? Type { get; set; } + public string? Type { get; set; } - public string? Description { get; set; } + public string? Description { get; set; } - public string? Name { get; set; } + public string? Name { get; set; } - public string? In { get; set; } + public string? In { get; set; } - public string? Flow { get; set; } + public string? Flow { get; set; } - public string? AuthorizationUrl { get; set; } + public string? AuthorizationUrl { get; set; } - public string? TokenUrl { get; set; } + public string? TokenUrl { get; set; } - public IDictionary? Scopes { get; set; } - } + public IDictionary? Scopes { get; set; } } diff --git a/src/Facility.Definition.Swagger/SwaggerService.cs b/src/Facility.Definition.Swagger/SwaggerService.cs index e1d496b..de53ee7 100644 --- a/src/Facility.Definition.Swagger/SwaggerService.cs +++ b/src/Facility.Definition.Swagger/SwaggerService.cs @@ -1,39 +1,34 @@ -using System.Collections.Generic; +namespace Facility.Definition.Swagger; -#pragma warning disable 1591 - -namespace Facility.Definition.Swagger +public class SwaggerService { - public class SwaggerService - { - public string? Swagger { get; set; } + public string? Swagger { get; set; } - public SwaggerInfo? Info { get; set; } + public SwaggerInfo? Info { get; set; } - public string? Host { get; set; } + public string? Host { get; set; } - public string? BasePath { get; set; } + public string? BasePath { get; set; } - public IList? Schemes { get; set; } + public IList? Schemes { get; set; } - public IList? Consumes { get; set; } + public IList? Consumes { get; set; } - public IList? Produces { get; set; } + public IList? Produces { get; set; } - public IDictionary? Paths { get; set; } + public IDictionary? Paths { get; set; } - public IDictionary? Definitions { get; set; } + public IDictionary? Definitions { get; set; } - public IDictionary? Parameters { get; set; } + public IDictionary? Parameters { get; set; } - public IDictionary? Responses { get; set; } + public IDictionary? Responses { get; set; } - public IDictionary? SecurityDefinitions { get; set; } + public IDictionary? SecurityDefinitions { get; set; } - public IList>>? Security { get; set; } + public IList>>? Security { get; set; } - public IList? Tags { get; set; } + public IList? Tags { get; set; } - public SwaggerExternalDocumentation? ExternalDocs { get; set; } - } + public SwaggerExternalDocumentation? ExternalDocs { get; set; } } diff --git a/src/Facility.Definition.Swagger/SwaggerTag.cs b/src/Facility.Definition.Swagger/SwaggerTag.cs index f6630f4..becc69e 100644 --- a/src/Facility.Definition.Swagger/SwaggerTag.cs +++ b/src/Facility.Definition.Swagger/SwaggerTag.cs @@ -1,13 +1,10 @@ -#pragma warning disable 1591 +namespace Facility.Definition.Swagger; -namespace Facility.Definition.Swagger +public class SwaggerTag { - public class SwaggerTag - { - public string? Name { get; set; } + public string? Name { get; set; } - public string? Description { get; set; } + public string? Description { get; set; } - public SwaggerExternalDocumentation? ExternalDocs { get; set; } - } + public SwaggerExternalDocumentation? ExternalDocs { get; set; } } diff --git a/src/Facility.Definition.Swagger/SwaggerUtility.cs b/src/Facility.Definition.Swagger/SwaggerUtility.cs index 02ba505..e1bf614 100644 --- a/src/Facility.Definition.Swagger/SwaggerUtility.cs +++ b/src/Facility.Definition.Swagger/SwaggerUtility.cs @@ -1,43 +1,40 @@ -using System; -using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -namespace Facility.Definition.Swagger +namespace Facility.Definition.Swagger; + +/// +/// Helpers for Swagger (OpenAPI) 2.0. +/// +public static class SwaggerUtility { /// - /// Helpers for Swagger (OpenAPI) 2.0. + /// The Swagger version. /// - public static class SwaggerUtility + public static readonly string SwaggerVersion = "2.0"; + + /// + /// JSON serializer settings for Swagger DTOs. + /// + public static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCaseExceptDictionaryKeysContractResolver(), + DateParseHandling = DateParseHandling.None, + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + MetadataPropertyHandling = MetadataPropertyHandling.Ignore, + }; + + internal static IReadOnlyList EmptyIfNull(this IReadOnlyList? list) => list ?? Array.Empty(); + + internal static IList EmptyIfNull(this IList? list) => list ?? Array.Empty(); + + internal static IReadOnlyDictionary EmptyIfNull(this IReadOnlyDictionary? list) => list ?? new Dictionary(); + + internal static IDictionary EmptyIfNull(this IDictionary? list) => list ?? new Dictionary(); + + private sealed class CamelCaseExceptDictionaryKeysContractResolver : CamelCasePropertyNamesContractResolver { - /// - /// The Swagger version. - /// - public static readonly string SwaggerVersion = "2.0"; - - /// - /// JSON serializer settings for Swagger DTOs. - /// - public static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings - { - ContractResolver = new CamelCaseExceptDictionaryKeysContractResolver(), - DateParseHandling = DateParseHandling.None, - NullValueHandling = NullValueHandling.Ignore, - MissingMemberHandling = MissingMemberHandling.Ignore, - MetadataPropertyHandling = MetadataPropertyHandling.Ignore, - }; - - internal static IReadOnlyList EmptyIfNull(this IReadOnlyList? list) => list ?? Array.Empty(); - - internal static IList EmptyIfNull(this IList? list) => list ?? Array.Empty(); - - internal static IReadOnlyDictionary EmptyIfNull(this IReadOnlyDictionary? list) => list ?? new Dictionary(); - - internal static IDictionary EmptyIfNull(this IDictionary? list) => list ?? new Dictionary(); - - private sealed class CamelCaseExceptDictionaryKeysContractResolver : CamelCasePropertyNamesContractResolver - { - protected override string ResolveDictionaryKey(string dictionaryKey) => dictionaryKey; - } + protected override string ResolveDictionaryKey(string dictionaryKey) => dictionaryKey; } } diff --git a/src/fsdgenswagger/FsdGenSwaggerApp.cs b/src/fsdgenswagger/FsdGenSwaggerApp.cs index d643d28..2f4d81e 100644 --- a/src/fsdgenswagger/FsdGenSwaggerApp.cs +++ b/src/fsdgenswagger/FsdGenSwaggerApp.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using ArgsReading; using Facility.CodeGen.Console; using Facility.Definition; @@ -6,43 +5,42 @@ using Facility.Definition.Fsd; using Facility.Definition.Swagger; -namespace fsdgenswagger +namespace fsdgenswagger; + +public sealed class FsdGenSwaggerApp : CodeGeneratorApp { - public sealed class FsdGenSwaggerApp : CodeGeneratorApp + public static int Main(string[] args) => new FsdGenSwaggerApp().Run(args); + + protected override IReadOnlyList Description => new[] { - public static int Main(string[] args) => new FsdGenSwaggerApp().Run(args); + "Converts Swagger (OpenAPI) 2.0 to/from a Facility Service Definition.", + }; - protected override IReadOnlyList Description => new[] - { - "Converts Swagger (OpenAPI) 2.0 to/from a Facility Service Definition.", - }; + protected override IReadOnlyList ExtraUsage => new[] + { + " --fsd", + " Generates a Facility Service Definition (instead of Swagger).", + " --json", + " Generates JSON (instead of YAML).", + " --service-name ", + " Overrides the service name.", + }; - protected override IReadOnlyList ExtraUsage => new[] - { - " --fsd", - " Generates a Facility Service Definition (instead of Swagger).", - " --json", - " Generates JSON (instead of YAML).", - " --service-name ", - " Overrides the service name.", - }; + protected override CodeGenerator CreateGenerator() => new SwaggerGenerator(); - protected override CodeGenerator CreateGenerator() => new SwaggerGenerator(); + protected override FileGeneratorSettings CreateSettings(ArgsReader args) + { + var serviceName = args.ReadOption("service-name"); + if (serviceName != null && ServiceDefinitionUtility.IsValidName(serviceName)) + throw new ArgsReaderException($"Invalid service name '{serviceName}'."); - protected override FileGeneratorSettings CreateSettings(ArgsReader args) + return new SwaggerGeneratorSettings { - var serviceName = args.ReadOption("service-name"); - if (serviceName != null && ServiceDefinitionUtility.IsValidName(serviceName)) - throw new ArgsReaderException($"Invalid service name '{serviceName}'."); - - return new SwaggerGeneratorSettings - { - GeneratesFsd = args.ReadFlag("fsd"), - GeneratesJson = args.ReadFlag("json"), - ServiceName = serviceName, - }; - } - - protected override ServiceParser CreateParser() => new SwaggerParser(); + GeneratesFsd = args.ReadFlag("fsd"), + GeneratesJson = args.ReadFlag("json"), + ServiceName = serviceName, + }; } + + protected override ServiceParser CreateParser() => new SwaggerParser(); } diff --git a/tests/Facility.Definition.Swagger.UnitTests/BrokenSwaggerTests.cs b/tests/Facility.Definition.Swagger.UnitTests/BrokenSwaggerTests.cs index 9b22b4d..c6b9eaf 100644 --- a/tests/Facility.Definition.Swagger.UnitTests/BrokenSwaggerTests.cs +++ b/tests/Facility.Definition.Swagger.UnitTests/BrokenSwaggerTests.cs @@ -1,41 +1,40 @@ using FluentAssertions; using NUnit.Framework; -namespace Facility.Definition.Swagger.UnitTests +namespace Facility.Definition.Swagger.UnitTests; + +[TestFixture] +public class BrokenSwaggerTests { - [TestFixture] - public class BrokenSwaggerTests + [TestCase("", "(1,1): Service definition is missing.")] + [TestCase("{", "(1,1): Unexpected end when reading JSON.")] + [TestCase(" {", "(1,2): Unexpected end when reading JSON.")] + [TestCase("# empty", "(1,1): Service definition is missing.")] + [TestCase("invalid", "(1,8): Invalid cast from")] + [TestCase(" \n {}", "(2,2): swagger field is missing.")] + [TestCase("info: {}", "(1,1): swagger field is missing.")] + [TestCase("{swagger:{}}", "(1,10): Unexpected character")] + [TestCase("swagger: {}", "(1,10): Failed to create an instance of type")] + [TestCase("{swagger:'1.0'}", "(1,14): swagger should be '2.0'.")] + [TestCase("swagger: '1.0'", "(1,15): swagger should be '2.0'.")] + [TestCase("{swagger:'2.0'}", "(1,1): info is missing.")] + [TestCase("swagger: '2.0'", "(1,1): info is missing.")] + [TestCase("{swagger:'2.0',info:{}}", "(1,21): info/title is missing.")] + [TestCase("swagger: '2.0'\ninfo: {}", "(2,7): info/title is missing.")] + [TestCase("{swagger:'2.0',info:{title:' '}}", "(1,30): info/title is not a valid service name.")] + [TestCase("swagger: '2.0'\ninfo:\n title: ' '\n", "(3,13): info/title is not a valid service name.")] + [TestCase("{swagger:'2.0',info:{title:' ','x-identifier':' '}}", "(1,49): info/x-identifier is not a valid service name.")] + [TestCase("swagger: '2.0'\ninfo:\n title: ' '\n x-identifier: ' '\n", "(4,20): info/x-identifier is not a valid service name.")] + public void SwaggerParseFailure(string text, string messagePrefix) { - [TestCase("", "(1,1): Service definition is missing.")] - [TestCase("{", "(1,1): Unexpected end when reading JSON.")] - [TestCase(" {", "(1,2): Unexpected end when reading JSON.")] - [TestCase("# empty", "(1,1): Service definition is missing.")] - [TestCase("invalid", "(1,8): Invalid cast from")] - [TestCase(" \n {}", "(2,2): swagger field is missing.")] - [TestCase("info: {}", "(1,1): swagger field is missing.")] - [TestCase("{swagger:{}}", "(1,10): Unexpected character")] - [TestCase("swagger: {}", "(1,10): Failed to create an instance of type")] - [TestCase("{swagger:'1.0'}", "(1,14): swagger should be '2.0'.")] - [TestCase("swagger: '1.0'", "(1,15): swagger should be '2.0'.")] - [TestCase("{swagger:'2.0'}", "(1,1): info is missing.")] - [TestCase("swagger: '2.0'", "(1,1): info is missing.")] - [TestCase("{swagger:'2.0',info:{}}", "(1,21): info/title is missing.")] - [TestCase("swagger: '2.0'\ninfo: {}", "(2,7): info/title is missing.")] - [TestCase("{swagger:'2.0',info:{title:' '}}", "(1,30): info/title is not a valid service name.")] - [TestCase("swagger: '2.0'\ninfo:\n title: ' '\n", "(3,13): info/title is not a valid service name.")] - [TestCase("{swagger:'2.0',info:{title:' ','x-identifier':' '}}", "(1,49): info/x-identifier is not a valid service name.")] - [TestCase("swagger: '2.0'\ninfo:\n title: ' '\n x-identifier: ' '\n", "(4,20): info/x-identifier is not a valid service name.")] - public void SwaggerParseFailure(string text, string messagePrefix) + try + { + new SwaggerParser().ParseDefinition(new ServiceDefinitionText("", text)); + Assert.Fail("Parse didn't fail."); + } + catch (ServiceDefinitionException exception) { - try - { - new SwaggerParser().ParseDefinition(new ServiceDefinitionText("", text)); - Assert.Fail("Parse didn't fail."); - } - catch (ServiceDefinitionException exception) - { - exception.Message.Should().StartWith(messagePrefix); - } + exception.Message.Should().StartWith(messagePrefix); } } } diff --git a/tests/Facility.Definition.Swagger.UnitTests/ComplexSwaggerTests.cs b/tests/Facility.Definition.Swagger.UnitTests/ComplexSwaggerTests.cs index 5593ad0..501bedb 100644 --- a/tests/Facility.Definition.Swagger.UnitTests/ComplexSwaggerTests.cs +++ b/tests/Facility.Definition.Swagger.UnitTests/ComplexSwaggerTests.cs @@ -1,34 +1,32 @@ -using System.Collections.Generic; -using System.Linq; using FluentAssertions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NUnit.Framework; -namespace Facility.Definition.Swagger.UnitTests +namespace Facility.Definition.Swagger.UnitTests; + +[TestFixture] +public class ComplexSwaggerTests { - [TestFixture] - public class ComplexSwaggerTests + [Test] + public void GenerateComplexService() { - [Test] - public void GenerateComplexService() - { - var generator = new SwaggerGenerator { GeneratesJson = true, GeneratorName = "tests" }; - var fsdService = TestUtility.ParseTestApi(c_fsdText); - var output = generator.GenerateOutput(fsdService); - var file = output.Files.Single(); - file.Name.Should().Be("TestApi.json"); - var jToken = JToken.Parse(file.Text); - var jTokenExpected = JToken.FromObject(s_swaggerService, JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings)); - if (!JToken.DeepEquals(jToken, jTokenExpected)) - jToken.ToString(Formatting.None).Should().Be(jTokenExpected.ToString(Formatting.None)); + var generator = new SwaggerGenerator { GeneratesJson = true, GeneratorName = "tests" }; + var fsdService = TestUtility.ParseTestApi(c_fsdText); + var output = generator.GenerateOutput(fsdService); + var file = output.Files.Single(); + file.Name.Should().Be("TestApi.json"); + var jToken = JToken.Parse(file.Text); + var jTokenExpected = JToken.FromObject(s_swaggerService, JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings)); + if (!JToken.DeepEquals(jToken, jTokenExpected)) + jToken.ToString(Formatting.None).Should().Be(jTokenExpected.ToString(Formatting.None)); - var service = new SwaggerParser().ParseDefinition(new ServiceDefinitionText(name: file.Name, text: file.Text)); - service.Summary.Should().Be(fsdService.Summary); - service.Methods.Count.Should().Be(fsdService.Methods.Count); - } + var service = new SwaggerParser().ParseDefinition(new ServiceDefinitionText(name: file.Name, text: file.Text)); + service.Summary.Should().Be(fsdService.Summary); + service.Methods.Count.Should().Be(fsdService.Methods.Count); + } - private const string c_fsdText = @" + private const string c_fsdText = @" /// test service [info(version: 1.2.3), http(url: ""https://example.com/v3"")] service TestApi @@ -82,158 +80,157 @@ These are the service remarks. They are multi-line. "; - private static readonly SwaggerService s_swaggerService = new SwaggerService + private static readonly SwaggerService s_swaggerService = new SwaggerService + { + Swagger = "2.0", + Info = new SwaggerInfo { - Swagger = "2.0", - Info = new SwaggerInfo - { - Title = "test service", - Description = "These are the service remarks.\n\nThey are multi-line.", - Version = "1.2.3", - Identifier = "TestApi", - CodeGen = "DO NOT EDIT: generated by tests", - }, - Host = "example.com", - BasePath = "/v3", - Schemes = new[] { "https" }, - Paths = new Dictionary + Title = "test service", + Description = "These are the service remarks.\n\nThey are multi-line.", + Version = "1.2.3", + Identifier = "TestApi", + CodeGen = "DO NOT EDIT: generated by tests", + }, + Host = "example.com", + BasePath = "/v3", + Schemes = new[] { "https" }, + Paths = new Dictionary + { + ["/do/{id}"] = new SwaggerOperations { - ["/do/{id}"] = new SwaggerOperations + Post = new SwaggerOperation { - Post = new SwaggerOperation + Summary = "do!", + OperationId = "do", + Consumes = new[] { "application/json" }, + Produces = new[] { "application/json" }, + Parameters = new[] { - Summary = "do!", - OperationId = "do", - Consumes = new[] { "application/json" }, - Produces = new[] { "application/json" }, - Parameters = new[] + new SwaggerParameter { - new SwaggerParameter - { - In = SwaggerParameterKind.Path, - Name = "id", - Description = "path!", - Required = true, - Type = SwaggerSchemaType.Integer, - Format = SwaggerSchemaTypeFormat.Int64, - }, - new SwaggerParameter - { - In = SwaggerParameterKind.Query, - Name = "q", - Description = "query!", - Type = SwaggerSchemaType.String, - Identifier = "query", - }, - new SwaggerParameter - { - In = SwaggerParameterKind.Body, - Name = "request", - Required = true, - Schema = new SwaggerSchema - { - Ref = "#/definitions/DoRequest", - }, - }, + In = SwaggerParameterKind.Path, + Name = "id", + Description = "path!", + Required = true, + Type = SwaggerSchemaType.Integer, + Format = SwaggerSchemaTypeFormat.Int64, + }, + new SwaggerParameter + { + In = SwaggerParameterKind.Query, + Name = "q", + Description = "query!", + Type = SwaggerSchemaType.String, + Identifier = "query", }, - Responses = new Dictionary + new SwaggerParameter { - ["201"] = new SwaggerResponse + In = SwaggerParameterKind.Body, + Name = "request", + Required = true, + Schema = new SwaggerSchema { - Description = "", - Schema = new SwaggerSchema - { - Ref = "#/definitions/DoResponse", - }, + Ref = "#/definitions/DoRequest", }, - ["202"] = new SwaggerResponse + }, + }, + Responses = new Dictionary + { + ["201"] = new SwaggerResponse + { + Description = "", + Schema = new SwaggerSchema { - Description = "job!", - Schema = new SwaggerSchema - { - Ref = "#/definitions/Job", - }, - Identifier = "job", + Ref = "#/definitions/DoResponse", }, - ["204"] = new SwaggerResponse + }, + ["202"] = new SwaggerResponse + { + Description = "job!", + Schema = new SwaggerSchema { - Description = "no ID!", - Identifier = "noId", + Ref = "#/definitions/Job", }, + Identifier = "job", + }, + ["204"] = new SwaggerResponse + { + Description = "no ID!", + Identifier = "noId", }, }, }, - ["/kill"] = new SwaggerOperations + }, + ["/kill"] = new SwaggerOperations + { + Post = new SwaggerOperation { - Post = new SwaggerOperation + Tags = new[] { "deadly", "admin" }, + OperationId = "kill", + Consumes = new[] { "application/json" }, + Parameters = new[] { - Tags = new[] { "deadly", "admin" }, - OperationId = "kill", - Consumes = new[] { "application/json" }, - Parameters = new[] + new SwaggerParameter { - new SwaggerParameter + In = SwaggerParameterKind.Body, + Name = "request", + Description = "job!", + Required = true, + Schema = new SwaggerSchema { - In = SwaggerParameterKind.Body, - Name = "request", - Description = "job!", - Required = true, - Schema = new SwaggerSchema - { - Ref = "#/definitions/Job", - }, + Ref = "#/definitions/Job", }, }, - Responses = new Dictionary + }, + Responses = new Dictionary + { + ["200"] = new SwaggerResponse { - ["200"] = new SwaggerResponse - { - Description = "", - }, + Description = "", }, }, }, }, - Definitions = new Dictionary + }, + Definitions = new Dictionary + { + ["Job"] = new SwaggerSchema { - ["Job"] = new SwaggerSchema + Type = SwaggerSchemaType.Object, + Description = "job data", + Properties = new Dictionary { - Type = SwaggerSchemaType.Object, - Description = "job data", - Properties = new Dictionary + ["jobId"] = new SwaggerSchema { - ["jobId"] = new SwaggerSchema - { - Type = SwaggerSchemaType.String, - }, + Type = SwaggerSchemaType.String, }, }, - ["DoRequest"] = new SwaggerSchema + }, + ["DoRequest"] = new SwaggerSchema + { + Type = SwaggerSchemaType.Object, + Properties = new Dictionary { - Type = SwaggerSchemaType.Object, - Properties = new Dictionary + ["field"] = new SwaggerSchema { - ["field"] = new SwaggerSchema - { - Description = "field!", - Type = SwaggerSchemaType.Boolean, - }, + Description = "field!", + Type = SwaggerSchemaType.Boolean, }, }, - ["DoResponse"] = new SwaggerSchema + }, + ["DoResponse"] = new SwaggerSchema + { + Type = SwaggerSchemaType.Object, + Properties = new Dictionary { - Type = SwaggerSchemaType.Object, - Properties = new Dictionary + ["id"] = new SwaggerSchema { - ["id"] = new SwaggerSchema - { - Description = "id!", - Type = SwaggerSchemaType.String, - }, + Description = "id!", + Type = SwaggerSchemaType.String, }, - Required = new[] { "id" }, }, + Required = new[] { "id" }, }, - }; - } + }, + }; } diff --git a/tests/Facility.Definition.Swagger.UnitTests/SimpleSwaggerTests.cs b/tests/Facility.Definition.Swagger.UnitTests/SimpleSwaggerTests.cs index d96bfed..0b51492 100644 --- a/tests/Facility.Definition.Swagger.UnitTests/SimpleSwaggerTests.cs +++ b/tests/Facility.Definition.Swagger.UnitTests/SimpleSwaggerTests.cs @@ -1,62 +1,59 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; using FluentAssertions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NUnit.Framework; using YamlDotNet.Serialization; -namespace Facility.Definition.Swagger.UnitTests +namespace Facility.Definition.Swagger.UnitTests; + +[TestFixture] +public class SimpleSwaggerTests { - [TestFixture] - public class SimpleSwaggerTests + [Test] + public void GenerateSimpleService() { - [Test] - public void GenerateSimpleService() - { - var service = new SwaggerParser().ConvertSwaggerService(s_swaggerService); - service.Summary.Should().Be("TestApi"); - service.Methods.Count.Should().Be(1); - } + var service = new SwaggerParser().ConvertSwaggerService(s_swaggerService); + service.Summary.Should().Be("TestApi"); + service.Methods.Count.Should().Be(1); + } - [Test] - public void GenerateSimpleServiceJson() - { - var generator = new SwaggerGenerator { GeneratesJson = true, GeneratorName = "tests" }; - var fsdService = TestUtility.ParseTestApi(c_fsdText); - var file = generator.GenerateOutput(fsdService).Files.Single(); - file.Name.Should().Be("TestApi.json"); - var jToken = JToken.Parse(file.Text); - var jTokenExpected = JToken.FromObject(s_swaggerService, JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings)); - JToken.DeepEquals(jToken, jTokenExpected).Should().BeTrue("{0} should be {1}", jToken, jTokenExpected); + [Test] + public void GenerateSimpleServiceJson() + { + var generator = new SwaggerGenerator { GeneratesJson = true, GeneratorName = "tests" }; + var fsdService = TestUtility.ParseTestApi(c_fsdText); + var file = generator.GenerateOutput(fsdService).Files.Single(); + file.Name.Should().Be("TestApi.json"); + var jToken = JToken.Parse(file.Text); + var jTokenExpected = JToken.FromObject(s_swaggerService, JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings)); + JToken.DeepEquals(jToken, jTokenExpected).Should().BeTrue("{0} should be {1}", jToken, jTokenExpected); - var service = new SwaggerParser().ParseDefinition(new ServiceDefinitionText(name: file.Name, text: file.Text)); - service.Summary.Should().Be("TestApi"); - service.Methods.Count.Should().Be(fsdService.Methods.Count); + var service = new SwaggerParser().ParseDefinition(new ServiceDefinitionText(name: file.Name, text: file.Text)); + service.Summary.Should().Be("TestApi"); + service.Methods.Count.Should().Be(fsdService.Methods.Count); - service = new SwaggerParser().ConvertSwaggerService(s_swaggerService); - service.Summary.Should().Be("TestApi"); - service.Methods.Count.Should().Be(fsdService.Methods.Count); - } + service = new SwaggerParser().ConvertSwaggerService(s_swaggerService); + service.Summary.Should().Be("TestApi"); + service.Methods.Count.Should().Be(fsdService.Methods.Count); + } - [Test] - public void GenerateSimpleServiceYaml() - { - var generator = new SwaggerGenerator { GeneratesJson = false, GeneratorName = "tests" }; - var fsdService = TestUtility.ParseTestApi(c_fsdText); - var file = generator.GenerateOutput(fsdService).Files.Single(); - file.Name.Should().Be("TestApi.yaml"); - var jToken = JToken.FromObject(new DeserializerBuilder().Build().Deserialize(new StringReader(file.Text))!); - var jTokenExpected = JToken.FromObject(s_swaggerService, JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings)); - JToken.DeepEquals(jToken, jTokenExpected).Should().BeTrue("{0} should be {1}", jToken, jTokenExpected); + [Test] + public void GenerateSimpleServiceYaml() + { + var generator = new SwaggerGenerator { GeneratesJson = false, GeneratorName = "tests" }; + var fsdService = TestUtility.ParseTestApi(c_fsdText); + var file = generator.GenerateOutput(fsdService).Files.Single(); + file.Name.Should().Be("TestApi.yaml"); + var jToken = JToken.FromObject(new DeserializerBuilder().Build().Deserialize(new StringReader(file.Text))!); + var jTokenExpected = JToken.FromObject(s_swaggerService, JsonSerializer.Create(SwaggerUtility.JsonSerializerSettings)); + JToken.DeepEquals(jToken, jTokenExpected).Should().BeTrue("{0} should be {1}", jToken, jTokenExpected); - var service = new SwaggerParser().ParseDefinition(new ServiceDefinitionText(name: file.Name, text: file.Text)); - service.Summary.Should().Be("TestApi"); - service.Methods.Count.Should().Be(fsdService.Methods.Count); - } + var service = new SwaggerParser().ParseDefinition(new ServiceDefinitionText(name: file.Name, text: file.Text)); + service.Summary.Should().Be("TestApi"); + service.Methods.Count.Should().Be(fsdService.Methods.Count); + } - private const string c_fsdText = @" + private const string c_fsdText = @" service TestApi { method do @@ -66,33 +63,32 @@ service TestApi } }"; - private static readonly SwaggerService s_swaggerService = new SwaggerService + private static readonly SwaggerService s_swaggerService = new SwaggerService + { + Swagger = "2.0", + Info = new SwaggerInfo { - Swagger = "2.0", - Info = new SwaggerInfo - { - Identifier = "TestApi", - Title = "TestApi", - Version = "0.0.0", - CodeGen = "DO NOT EDIT: generated by tests", - }, - Paths = new Dictionary + Identifier = "TestApi", + Title = "TestApi", + Version = "0.0.0", + CodeGen = "DO NOT EDIT: generated by tests", + }, + Paths = new Dictionary + { + ["/do"] = new SwaggerOperations { - ["/do"] = new SwaggerOperations + Post = new SwaggerOperation { - Post = new SwaggerOperation + OperationId = "do", + Responses = new Dictionary { - OperationId = "do", - Responses = new Dictionary + ["200"] = new SwaggerResponse { - ["200"] = new SwaggerResponse - { - Description = "", - }, + Description = "", }, }, }, }, - }; - } + }, + }; } diff --git a/tests/Facility.Definition.Swagger.UnitTests/TestUtility.cs b/tests/Facility.Definition.Swagger.UnitTests/TestUtility.cs index 33bbfcd..a9c154f 100644 --- a/tests/Facility.Definition.Swagger.UnitTests/TestUtility.cs +++ b/tests/Facility.Definition.Swagger.UnitTests/TestUtility.cs @@ -1,12 +1,11 @@ using Facility.Definition.Fsd; -namespace Facility.Definition.Swagger.UnitTests +namespace Facility.Definition.Swagger.UnitTests; + +internal static class TestUtility { - internal static class TestUtility + public static ServiceInfo ParseTestApi(string text) { - public static ServiceInfo ParseTestApi(string text) - { - return new FsdParser().ParseDefinition(new ServiceDefinitionText("TestApi.fsd", text)); - } + return new FsdParser().ParseDefinition(new ServiceDefinitionText("TestApi.fsd", text)); } } diff --git a/tools/Build/Build.cs b/tools/Build/Build.cs index 5d1a5cd..da162df 100644 --- a/tools/Build/Build.cs +++ b/tools/Build/Build.cs @@ -1,10 +1,3 @@ -using System; -using System.IO; -using System.Linq; -using Faithlife.Build; -using static Faithlife.Build.BuildUtility; -using static Faithlife.Build.DotNetRunner; - return BuildRunner.Execute(args, build => { var codegen = "fsdgenswagger"; diff --git a/tools/Build/Build.csproj b/tools/Build/Build.csproj index 0fbf7e9..3fda495 100644 --- a/tools/Build/Build.csproj +++ b/tools/Build/Build.csproj @@ -6,7 +6,7 @@ - + diff --git a/tools/XmlDocGen/XmlDocGen.csproj b/tools/XmlDocGen/XmlDocGen.csproj index 1fe4bea..1a90ee4 100644 --- a/tools/XmlDocGen/XmlDocGen.csproj +++ b/tools/XmlDocGen/XmlDocGen.csproj @@ -10,7 +10,7 @@ - +