diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..49de0d370 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,123 @@ +# To learn more about .editorconfig see https://aka.ms/editorconfigdocs +############################### +# Core EditorConfig Options # +############################### +# All files +[*] +indent_style = space +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom +############################### +# .NET Coding Conventions # +############################### +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +# this. preferences +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_readonly_field = true:suggestion +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +############################### +# Naming Conventions # +############################### +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const +############################### +# C# Coding Conventions # +############################### +[*.cs] +# var preferences +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +############################### +# C# Formatting Rules # +############################### +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation preferences +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true +############################### +# VB Coding Conventions # +############################### +[*.vb] +# Modifier preferences +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion diff --git a/Microsoft.OpenApi.sln b/Microsoft.OpenApi.sln index d957905d4..e64ff3a24 100644 --- a/Microsoft.OpenApi.sln +++ b/Microsoft.OpenApi.sln @@ -1,12 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2027 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi", "src\Microsoft.OpenApi\Microsoft.OpenApi.csproj", "{A8E50143-69B2-472A-9D45-3F9A05D13202}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4AEDAD90-F854-4940-BFEE-6374CC92CAB0}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig build.cmd = build.cmd readme.md = readme.md EndProjectSection @@ -25,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6357D7FD-2 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.SmokeTests", "test\Microsoft.OpenApi.SmokeTests\Microsoft.OpenApi.SmokeTests.csproj", "{AD79B61D-88CF-497C-9ED5-41AE3867C5AC}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.OpenApi.Tool", "src\Microsoft.OpenApi.Tool\Microsoft.OpenApi.Tool.csproj", "{254841B5-7DAC-4D1D-A9C5-44FE5CE467BE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,6 +58,10 @@ Global {AD79B61D-88CF-497C-9ED5-41AE3867C5AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {AD79B61D-88CF-497C-9ED5-41AE3867C5AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD79B61D-88CF-497C-9ED5-41AE3867C5AC}.Release|Any CPU.Build.0 = Release|Any CPU + {254841B5-7DAC-4D1D-A9C5-44FE5CE467BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {254841B5-7DAC-4D1D-A9C5-44FE5CE467BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {254841B5-7DAC-4D1D-A9C5-44FE5CE467BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {254841B5-7DAC-4D1D-A9C5-44FE5CE467BE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -66,6 +73,7 @@ Global {AD83F991-DBF3-4251-8613-9CC54C826964} = {6357D7FD-2DE4-4900-ADB9-ABC37052040A} {1ED3C2C1-E1E7-4925-B4E6-2D969C3F5237} = {6357D7FD-2DE4-4900-ADB9-ABC37052040A} {AD79B61D-88CF-497C-9ED5-41AE3867C5AC} = {6357D7FD-2DE4-4900-ADB9-ABC37052040A} + {254841B5-7DAC-4D1D-A9C5-44FE5CE467BE} = {E546B92F-20A8-49C3-8323-4B25BB78F3E1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9F171EFC-0DB5-4B10-ABFA-AF48D52CC565} diff --git a/README.md b/README.md index 36fd15dbc..bce79b6a3 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ Project Objectives - Provide OpenAPI description writers for both V2 and V3 specification formats. - Enable developers to create Readers that translate different data formats into OpenAPI descriptions. +# Installation + +- Install core Nuget package `Microsoft.OpenApi` +- Install readers Nuget package `Microsoft.OpenApi.Readers` + # Processors The OpenAPI.NET project holds the base object model for representing OpenAPI documents as .NET objects. Some developers have found the need to write processors that convert other data formats into this OpenAPI.NET object model. We'd like to curate that list of processors in this section of the readme. diff --git a/build.cmd b/build.cmd index cbae2cb0e..3c65e48bd 100644 --- a/build.cmd +++ b/build.cmd @@ -1,21 +1,17 @@ @echo off -if "%~1"=="" goto :error - -SET VERSION=%~1 - -Echo Building Microsoft.OpenApi +Echo Building Microsoft.OpenApi SET PROJ=%~dp0src\Microsoft.OpenApi\Microsoft.OpenApi.csproj -dotnet build %PROJ% /t:restore /p:Configuration=Release -dotnet build %PROJ% /t:build /p:Configuration=Release -dotnet build %PROJ% /t:pack /p:Configuration=Release;PackageOutputPath=%~dp0artifacts;Version=%VERSION% +dotnet msbuild %PROJ% /t:restore /p:Configuration=Release +dotnet msbuild %PROJ% /t:build /p:Configuration=Release +dotnet msbuild %PROJ% /t:pack /p:Configuration=Release;PackageOutputPath=%~dp0artifacts Echo Building Microsoft.OpenApi.Readers SET PROJ=%~dp0src\Microsoft.OpenApi.Readers\Microsoft.OpenApi.Readers.csproj -dotnet build %PROJ% /t:restore /p:Configuration=Release -dotnet build %PROJ% /t:build /p:Configuration=Release -dotnet build %PROJ% /t:pack /p:Configuration=Release;PackageOutputPath=%~dp0artifacts;Version=%VERSION% +dotnet msbuild %PROJ% /t:restore /p:Configuration=Release +dotnet msbuild %PROJ% /t:build /p:Configuration=Release +dotnet msbuild %PROJ% /t:pack /p:Configuration=Release;PackageOutputPath=%~dp0artifacts goto :end :error diff --git a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs b/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs index ee829d6b3..03f80c93e 100644 --- a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs +++ b/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiReaderException.cs @@ -33,7 +33,7 @@ public OpenApiReaderException(string message, YamlNode node) : base(message) { // This only includes line because using a char range causes tests to break due to CR/LF & LF differences // See https://tools.ietf.org/html/rfc5147 for syntax - Pointer = $"#line={node.Start.Line}"; + Pointer = $"#line={node.Start.Line}"; } /// @@ -43,4 +43,4 @@ public OpenApiReaderException(string message, YamlNode node) : base(message) /// Inner exception that caused this exception to be thrown. public OpenApiReaderException(string message, Exception innerException) : base(message, innerException) { } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiUnsupportedSpecVersionException.cs b/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiUnsupportedSpecVersionException.cs index 199020784..705b212d0 100644 --- a/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiUnsupportedSpecVersionException.cs +++ b/src/Microsoft.OpenApi.Readers/Exceptions/OpenApiUnsupportedSpecVersionException.cs @@ -41,4 +41,4 @@ public OpenApiUnsupportedSpecVersionException(string specificationVersion, Excep /// public string SpecificationVersion { get; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/Interface/IDiagnostic.cs b/src/Microsoft.OpenApi.Readers/Interface/IDiagnostic.cs index 22d31e7a7..da3381f7e 100644 --- a/src/Microsoft.OpenApi.Readers/Interface/IDiagnostic.cs +++ b/src/Microsoft.OpenApi.Readers/Interface/IDiagnostic.cs @@ -9,4 +9,4 @@ namespace Microsoft.OpenApi.Readers.Interface public interface IDiagnostic { } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiReader.cs b/src/Microsoft.OpenApi.Readers/Interface/IOpenApiReader.cs index 170cde6fb..39724b3c6 100644 --- a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiReader.cs +++ b/src/Microsoft.OpenApi.Readers/Interface/IOpenApiReader.cs @@ -20,4 +20,4 @@ public interface IOpenApiReader where TDiagnostic : IDiagno /// The Open API document. OpenApiDocument Read(TInput input, out TDiagnostic diagnostic); } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs b/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs index 32dd420f4..a7a98d781 100644 --- a/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs +++ b/src/Microsoft.OpenApi.Readers/Interface/IOpenApiVersionService.cs @@ -37,4 +37,4 @@ internal interface IOpenApiVersionService /// Instance of OpenApiDocument populated with data from rootNode OpenApiDocument LoadDocument(RootNode rootNode); } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj index f519fd340..931d296ac 100644 --- a/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj +++ b/src/Microsoft.OpenApi.Readers/Microsoft.OpenApi.Readers.csproj @@ -10,7 +10,7 @@ Microsoft Microsoft.OpenApi.Readers Microsoft.OpenApi.Readers - 1.1.4 + 1.2.0 OpenAPI.NET Readers for JSON and YAML documents © Microsoft Corporation. All rights reserved. OpenAPI .NET @@ -26,7 +26,7 @@ - + diff --git a/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs b/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs index cd3258eaa..ea11c7939 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiDiagnostic.cs @@ -22,4 +22,4 @@ public class OpenApiDiagnostic : IDiagnostic /// public OpenApiSpecVersion SpecificationVersion { get; set; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs b/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs index 1b1c2f367..092699857 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiReaderSettings.cs @@ -1,4 +1,7 @@ -using Microsoft.OpenApi.Any; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Readers.ParseNodes; using Microsoft.OpenApi.Validations; @@ -42,7 +45,7 @@ public class OpenApiReaderSettings /// /// Dictionary of parsers for converting extensions into strongly typed classes /// - public Dictionary> ExtensionParsers { get; set; } = new Dictionary>(); + public Dictionary> ExtensionParsers { get; set; } = new Dictionary>(); /// /// Rules to use for validating OpenAPI specification. If none are provided a default set of rules are applied. @@ -52,6 +55,6 @@ public class OpenApiReaderSettings /// /// URL where relative references should be resolved from if the description does not contain Server definitions /// - public Uri BaseUrl { get; set; } + public Uri BaseUrl { get; set; } } } diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs index 1e0c08695..9dc14a7bd 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiStreamReader.cs @@ -1,18 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; using System.IO; -using System.Linq; -using Microsoft.OpenApi.Exceptions; -using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.Interface; -using Microsoft.OpenApi.Readers.Services; -using Microsoft.OpenApi.Services; -using SharpYaml; -using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers { @@ -21,7 +13,7 @@ namespace Microsoft.OpenApi.Readers /// public class OpenApiStreamReader : IOpenApiReader { - private OpenApiReaderSettings _settings; + private readonly OpenApiReaderSettings _settings; /// /// Create stream reader with custom settings if desired. @@ -30,8 +22,8 @@ public class OpenApiStreamReader : IOpenApiReader public OpenApiStreamReader(OpenApiReaderSettings settings = null) { _settings = settings ?? new OpenApiReaderSettings(); - } + /// /// Reads the stream input and parses it into an Open API document. /// @@ -40,68 +32,10 @@ public OpenApiStreamReader(OpenApiReaderSettings settings = null) /// Instance of newly created OpenApiDocument public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic) { - ParsingContext context; - YamlDocument yamlDocument; - diagnostic = new OpenApiDiagnostic(); - - // Parse the YAML/JSON - try - { - yamlDocument = LoadYamlDocument(input); - } - catch (YamlException ex) - { - diagnostic.Errors.Add(new OpenApiError($"#char={ex.Start.Line}", ex.Message)); - return new OpenApiDocument(); - } - - context = new ParsingContext - { - ExtensionParsers = _settings.ExtensionParsers, - BaseUrl = _settings.BaseUrl - }; - - OpenApiDocument document = null; - - try - { - // Parse the OpenAPI Document - document = context.Parse(yamlDocument, diagnostic); - - // Resolve References if requested - switch (_settings.ReferenceResolution) - { - case ReferenceResolutionSetting.ResolveAllReferences: - throw new ArgumentException(Properties.SRResource.CannotResolveRemoteReferencesSynchronously); - case ReferenceResolutionSetting.ResolveLocalReferences: - var resolver = new OpenApiReferenceResolver(document); - var walker = new OpenApiWalker(resolver); - walker.Walk(document); - foreach (var item in resolver.Errors) - { - diagnostic.Errors.Add(item); - } - break; - case ReferenceResolutionSetting.DoNotResolveReferences: - break; - } - } - catch (OpenApiException ex) - { - diagnostic.Errors.Add(new OpenApiError(ex)); - } - - // Validate the document - if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0) + using (var reader = new StreamReader(input)) { - var errors = document.Validate(_settings.RuleSet); - foreach (var item in errors) - { - diagnostic.Errors.Add(item); - } + return new OpenApiTextReaderReader(_settings).Read(reader, out diagnostic); } - - return document; } /// @@ -113,66 +47,10 @@ public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic) /// Instance of newly created OpenApiDocument public T ReadFragment(Stream input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiElement { - ParsingContext context; - YamlDocument yamlDocument; - diagnostic = new OpenApiDiagnostic(); - - // Parse the YAML/JSON - try - { - yamlDocument = LoadYamlDocument(input); - } - catch (YamlException ex) - { - diagnostic.Errors.Add(new OpenApiError($"#line={ex.Start.Line}", ex.Message)); - return default(T); - } - - context = new ParsingContext - { - ExtensionParsers = _settings.ExtensionParsers - }; - - IOpenApiElement element = null; - - try - { - // Parse the OpenAPI element - element = context.ParseFragment(yamlDocument, version, diagnostic); - } - catch (OpenApiException ex) - { - diagnostic.Errors.Add(new OpenApiError(ex)); - } - - // Validate the element - if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0) - { - var errors = element.Validate(_settings.RuleSet); - foreach (var item in errors) - { - diagnostic.Errors.Add(item); - } - } - - return (T)element; - } - - /// - /// Helper method to turn streams into YamlDocument - /// - /// Stream containing YAML formatted text - /// Instance of a YamlDocument - internal static YamlDocument LoadYamlDocument(Stream input) - { - YamlDocument yamlDocument; - using (var streamReader = new StreamReader(input)) + using (var reader = new StreamReader(input)) { - var yamlStream = new YamlStream(); - yamlStream.Load(streamReader); - yamlDocument = yamlStream.Documents.First(); + return new OpenApiTextReaderReader(_settings).ReadFragment(reader, version, out diagnostic); } - return yamlDocument; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/OpenApiStringReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiStringReader.cs index 82b3a3ce7..0cb9605dd 100644 --- a/src/Microsoft.OpenApi.Readers/OpenApiStringReader.cs +++ b/src/Microsoft.OpenApi.Readers/OpenApiStringReader.cs @@ -29,14 +29,9 @@ public OpenApiStringReader(OpenApiReaderSettings settings = null) /// public OpenApiDocument Read(string input, out OpenApiDiagnostic diagnostic) { - using (var memoryStream = new MemoryStream()) + using (var reader = new StringReader(input)) { - var writer = new StreamWriter(memoryStream); - writer.Write(input); - writer.Flush(); - memoryStream.Position = 0; - - return new OpenApiStreamReader(_settings).Read(memoryStream, out diagnostic); + return new OpenApiTextReaderReader(_settings).Read(reader, out diagnostic); } } @@ -45,15 +40,10 @@ public OpenApiDocument Read(string input, out OpenApiDiagnostic diagnostic) /// public T ReadFragment(string input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiElement { - using (var memoryStream = new MemoryStream()) + using (var reader = new StringReader(input)) { - var writer = new StreamWriter(memoryStream); - writer.Write(input); - writer.Flush(); - memoryStream.Position = 0; - - return new OpenApiStreamReader(_settings).ReadFragment(memoryStream, version, out diagnostic); + return new OpenApiTextReaderReader(_settings).ReadFragment(reader, version, out diagnostic); } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs new file mode 100644 index 000000000..107454796 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/OpenApiTextReaderReader.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System.IO; +using System.Linq; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.Interface; +using SharpYaml; +using SharpYaml.Serialization; + +namespace Microsoft.OpenApi.Readers +{ + /// + /// Service class for converting contents of TextReader into OpenApiDocument instances + /// + public class OpenApiTextReaderReader : IOpenApiReader + { + private readonly OpenApiReaderSettings _settings; + + /// + /// Create stream reader with custom settings if desired. + /// + /// + public OpenApiTextReaderReader(OpenApiReaderSettings settings = null) + { + _settings = settings ?? new OpenApiReaderSettings(); + } + + /// + /// Reads the stream input and parses it into an Open API document. + /// + /// TextReader containing OpenAPI description to parse. + /// Returns diagnostic object containing errors detected during parsing + /// Instance of newly created OpenApiDocument + public OpenApiDocument Read(TextReader input, out OpenApiDiagnostic diagnostic) + { + YamlDocument yamlDocument; + + // Parse the YAML/JSON text in the TextReader into the YamlDocument + try + { + yamlDocument = LoadYamlDocument(input); + } + catch (YamlException ex) + { + diagnostic = new OpenApiDiagnostic(); + diagnostic.Errors.Add(new OpenApiError($"#char={ex.Start.Line}", ex.Message)); + return new OpenApiDocument(); + } + + return new OpenApiYamlDocumentReader(this._settings).Read(yamlDocument, out diagnostic); + } + /// + /// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element. + /// + /// TextReader containing OpenAPI description to parse. + /// Version of the OpenAPI specification that the fragment conforms to. + /// Returns diagnostic object containing errors detected during parsing + /// Instance of newly created OpenApiDocument + public T ReadFragment(TextReader input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiElement + { + YamlDocument yamlDocument; + + // Parse the YAML/JSON + try + { + yamlDocument = LoadYamlDocument(input); + } + catch (YamlException ex) + { + diagnostic = new OpenApiDiagnostic(); + diagnostic.Errors.Add(new OpenApiError($"#line={ex.Start.Line}", ex.Message)); + return default(T); + } + + return new OpenApiYamlDocumentReader(this._settings).ReadFragment(yamlDocument, version, out diagnostic); + } + + /// + /// Helper method to turn streams into YamlDocument + /// + /// Stream containing YAML formatted text + /// Instance of a YamlDocument + static YamlDocument LoadYamlDocument(TextReader input) + { + var yamlStream = new YamlStream(); + yamlStream.Load(input); + return yamlStream.Documents.First(); + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs new file mode 100644 index 000000000..73a31eac3 --- /dev/null +++ b/src/Microsoft.OpenApi.Readers/OpenApiYamlDocumentReader.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers.Interface; +using Microsoft.OpenApi.Readers.Services; +using Microsoft.OpenApi.Services; +using SharpYaml.Serialization; + +namespace Microsoft.OpenApi.Readers +{ + /// + /// Service class for converting contents of TextReader into OpenApiDocument instances + /// + internal class OpenApiYamlDocumentReader : IOpenApiReader + { + private readonly OpenApiReaderSettings _settings; + + /// + /// Create stream reader with custom settings if desired. + /// + /// + public OpenApiYamlDocumentReader(OpenApiReaderSettings settings = null) + { + _settings = settings ?? new OpenApiReaderSettings(); + } + + /// + /// Reads the stream input and parses it into an Open API document. + /// + /// TextReader containing OpenAPI description to parse. + /// Returns diagnostic object containing errors detected during parsing + /// Instance of newly created OpenApiDocument + public OpenApiDocument Read(YamlDocument input, out OpenApiDiagnostic diagnostic) + { + diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic) + { + ExtensionParsers = _settings.ExtensionParsers, + BaseUrl = _settings.BaseUrl + }; + + OpenApiDocument document = null; + try + { + // Parse the OpenAPI Document + document = context.Parse(input); + + // Resolve References if requested + switch (_settings.ReferenceResolution) + { + case ReferenceResolutionSetting.ResolveAllReferences: + throw new ArgumentException(Properties.SRResource.CannotResolveRemoteReferencesSynchronously); + case ReferenceResolutionSetting.ResolveLocalReferences: + var resolver = new OpenApiReferenceResolver(document); + var walker = new OpenApiWalker(resolver); + walker.Walk(document); + foreach (var item in resolver.Errors) + { + diagnostic.Errors.Add(item); + } + break; + case ReferenceResolutionSetting.DoNotResolveReferences: + break; + } + } + catch (OpenApiException ex) + { + diagnostic.Errors.Add(new OpenApiError(ex)); + } + + // Validate the document + if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0) + { + var errors = document.Validate(_settings.RuleSet); + foreach (var item in errors) + { + diagnostic.Errors.Add(item); + } + } + + return document; + } + /// + /// Reads the stream input and parses the fragment of an OpenAPI description into an Open API Element. + /// + /// TextReader containing OpenAPI description to parse. + /// Version of the OpenAPI specification that the fragment conforms to. + /// Returns diagnostic object containing errors detected during parsing + /// Instance of newly created OpenApiDocument + public T ReadFragment(YamlDocument input, OpenApiSpecVersion version, out OpenApiDiagnostic diagnostic) where T : IOpenApiElement + { + diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic) + { + ExtensionParsers = _settings.ExtensionParsers + }; + + IOpenApiElement element = null; + try + { + // Parse the OpenAPI element + element = context.ParseFragment(input, version); + } + catch (OpenApiException ex) + { + diagnostic.Errors.Add(new OpenApiError(ex)); + } + + // Validate the element + if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0) + { + var errors = element.Validate(_settings.RuleSet); + foreach (var item in errors) + { + diagnostic.Errors.Add(item); + } + } + + return (T)element; + } + } +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMap.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMap.cs index ef11c448b..a135f7f02 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMap.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/AnyFieldMap.cs @@ -8,4 +8,4 @@ namespace Microsoft.OpenApi.Readers.ParseNodes internal class AnyFieldMap : Dictionary> { } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMap.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMap.cs index acae8a976..dbc9eabeb 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMap.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/AnyListFieldMap.cs @@ -9,4 +9,4 @@ internal class AnyListFieldMap : Dictionary : Dictionary : Dictionary> { } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/JsonPointerExtensions.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/JsonPointerExtensions.cs index de4d14aa3..d30863955 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/JsonPointerExtensions.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/JsonPointerExtensions.cs @@ -53,4 +53,4 @@ public static YamlNode Find(this JsonPointer currentPointer, YamlNode baseYamlNo } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs index ee3553ad4..e1149cc5a 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ListNode.cs @@ -17,9 +17,8 @@ internal class ListNode : ParseNode, IEnumerable { private readonly YamlSequenceNode _nodeList; - public ListNode(ParsingContext context, OpenApiDiagnostic diagnostic, YamlSequenceNode sequenceNode) : base( - context, - diagnostic) + public ListNode(ParsingContext context, YamlSequenceNode sequenceNode) : base( + context) { _nodeList = sequenceNode; } @@ -32,14 +31,14 @@ public override List CreateList(Func map) $"Expected list at line {_nodeList.Start.Line} while parsing {typeof(T).Name}"); } - return _nodeList.Select(n => map(new MapNode(Context, Diagnostic, n as YamlMappingNode))) + return _nodeList.Select(n => map(new MapNode(Context, n as YamlMappingNode))) .Where(i => i != null) .ToList(); } public override List CreateListOfAny() { - return _nodeList.Select(n => ParseNode.Create(Context, Diagnostic,n).CreateAny()) + return _nodeList.Select(n => ParseNode.Create(Context, n).CreateAny()) .Where(i => i != null) .ToList(); } @@ -52,12 +51,12 @@ public override List CreateSimpleList(Func map) $"Expected list at line {_nodeList.Start.Line} while parsing {typeof(T).Name}"); } - return _nodeList.Select(n => map(new ValueNode(Context, Diagnostic, n))).ToList(); + return _nodeList.Select(n => map(new ValueNode(Context, n))).ToList(); } public IEnumerator GetEnumerator() { - return _nodeList.Select(n => Create(Context, Diagnostic, n)).ToList().GetEnumerator(); + return _nodeList.Select(n => Create(Context, n)).ToList().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() @@ -80,4 +79,4 @@ public override IOpenApiAny CreateAny() return array; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs index 95aa4ff62..26fc81076 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/MapNode.cs @@ -23,14 +23,13 @@ internal class MapNode : ParseNode, IEnumerable private readonly YamlMappingNode _node; private readonly List _nodes; - public MapNode(ParsingContext context, OpenApiDiagnostic diagnostic, string yamlString) : - this(context, diagnostic, (YamlMappingNode)YamlHelper.ParseYamlString(yamlString)) + public MapNode(ParsingContext context, string yamlString) : + this(context, (YamlMappingNode)YamlHelper.ParseYamlString(yamlString)) { } - public MapNode(ParsingContext context, OpenApiDiagnostic diagnostic, YamlNode node) : base( - context, - diagnostic) + public MapNode(ParsingContext context, YamlNode node) : base( + context) { if (!(node is YamlMappingNode mapNode)) { @@ -40,7 +39,7 @@ public MapNode(ParsingContext context, OpenApiDiagnostic diagnostic, YamlNode no this._node = mapNode; _nodes = this._node.Children - .Select(kvp => new PropertyNode(Context, Diagnostic, kvp.Key.GetScalarValue(), kvp.Value)) + .Select(kvp => new PropertyNode(Context, kvp.Key.GetScalarValue(), kvp.Value)) .Cast() .ToList(); } @@ -52,7 +51,7 @@ public PropertyNode this[string key] YamlNode node = null; if (this._node.Children.TryGetValue(new YamlScalarNode(key), out node)) { - return new PropertyNode(Context, Diagnostic, key, this._node.Children[new YamlScalarNode(key)]); + return new PropertyNode(Context, key, this._node.Children[new YamlScalarNode(key)]); } return null; @@ -73,43 +72,48 @@ public override Dictionary CreateMap(Func map) key = n.Key.GetScalarValue(), value = n.Value as YamlMappingNode == null ? default(T) - : map(new MapNode(Context, Diagnostic, n.Value as YamlMappingNode)) + : map(new MapNode(Context, n.Value as YamlMappingNode)) }); return nodes.ToDictionary(k => k.key, v => v.value); } - public override Dictionary CreateMapWithReference( - ReferenceType referenceType, - Func map) - { - var yamlMap = _node; - if (yamlMap == null) - { - throw new OpenApiException($"Expected map at line {yamlMap.Start.Line} while parsing {typeof(T).Name}"); - } - - var nodes = yamlMap.Select( - n => { - var entry = new - { - key = n.Key.GetScalarValue(), - value = map(new MapNode(Context, Diagnostic, (YamlMappingNode)n.Value)) - }; - if (entry.value == null) - { - return null; // Body Parameters shouldn't be converted to Parameters - } - entry.value.Reference = new OpenApiReference() - { - Type = referenceType, - Id = entry.key - }; - return entry; - } - ); - return nodes.Where(n => n!= null).ToDictionary(k => k.key, v => v.value); - } + public override Dictionary CreateMapWithReference( + ReferenceType referenceType, + Func map) + { + var yamlMap = _node; + if (yamlMap == null) + { + throw new OpenApiException($"Expected map at line {yamlMap.Start.Line} while parsing {typeof(T).Name}"); + } + + var nodes = yamlMap.Select( + n => + { + var entry = new + { + key = n.Key.GetScalarValue(), + value = map(new MapNode(Context, (YamlMappingNode)n.Value)) + }; + if (entry.value == null) + { + return null; // Body Parameters shouldn't be converted to Parameters + } + // If the component isn't a reference to another component, then point it to itself. + if (entry.value.Reference == null) + { + entry.value.Reference = new OpenApiReference() + { + Type = referenceType, + Id = entry.key + }; + } + return entry; + } + ); + return nodes.Where(n => n != null).ToDictionary(k => k.key, v => v.value); + } public override Dictionary CreateSimpleMap(Func map) { @@ -123,7 +127,7 @@ public override Dictionary CreateSimpleMap(Func map) n => new { key = n.Key.GetScalarValue(), - value = map(new ValueNode(Context, Diagnostic, (YamlScalarNode)n.Value)) + value = map(new ValueNode(Context, (YamlScalarNode)n.Value)) }); return nodes.ToDictionary(k => k.key, v => v.value); } @@ -140,7 +144,7 @@ IEnumerator IEnumerable.GetEnumerator() public override string GetRaw() { - var x = new Serializer(new SerializerSettings(new JsonSchema()) {EmitJsonComptible = true}); + var x = new Serializer(new SerializerSettings(new JsonSchema()) { EmitJsonComptible = true }); return x.Serialize(_node); } @@ -148,10 +152,10 @@ public T GetReferencedObject(ReferenceType referenceType, string referenceId) where T : IOpenApiReferenceable, new() { return new T() - { - UnresolvedReference = true, - Reference = Context.VersionService.ConvertToOpenApiReference(referenceId,referenceType) - }; + { + UnresolvedReference = true, + Reference = Context.VersionService.ConvertToOpenApiReference(referenceId, referenceType) + }; } public string GetReferencePointer() @@ -192,4 +196,4 @@ public override IOpenApiAny CreateAny() return apiObject; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs index 50ec431cc..ae9254fe8 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Text; using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Readers.ParseNodes @@ -15,16 +14,18 @@ internal static class OpenApiAnyConverter { /// /// Converts the s in the given - /// into the most specific type based on the value. + /// into the appropriate type based on the given . + /// For those strings that the schema does not specify the type for, convert them into + /// the most specific type based on the value. /// - public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny) + public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiSchema schema = null) { if (openApiAny is OpenApiArray openApiArray) { var newArray = new OpenApiArray(); foreach (var element in openApiArray) { - newArray.Add(GetSpecificOpenApiAny(element)); + newArray.Add(GetSpecificOpenApiAny(element, schema?.Items)); } return newArray; @@ -36,223 +37,232 @@ public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny) foreach (var key in openApiObject.Keys.ToList()) { - newObject[key] = GetSpecificOpenApiAny(openApiObject[key]); + if (schema?.Properties != null && schema.Properties.TryGetValue(key, out var property)) + { + newObject[key] = GetSpecificOpenApiAny(openApiObject[key], property); + } + else + { + newObject[key] = GetSpecificOpenApiAny(openApiObject[key], schema?.AdditionalProperties); + } } return newObject; } - if ( !(openApiAny is OpenApiString)) + if (!(openApiAny is OpenApiString)) { return openApiAny; } var value = ((OpenApiString)openApiAny).Value; + var type = schema?.Type; + var format = schema?.Format; - if (value == null || value == "null") - { - return new OpenApiNull(); - } - - if (value == "true") - { - return new OpenApiBoolean(true); - } - - if (value == "false") - { - return new OpenApiBoolean(false); - } - - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) - { - return new OpenApiInteger(intValue); - } - - if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue)) - { - return new OpenApiLong(longValue); - } - - if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue)) - { - return new OpenApiDouble(doubleValue); - } - - if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue)) + if (((OpenApiString)openApiAny).IsExplicit()) { - return new OpenApiDateTime(dateTimeValue); - } - - // if we can't identify the type of value, return it as string. - return new OpenApiString(value); - } - - /// - /// Converts the s in the given - /// into the appropriate type based on the given . - /// For those strings that the schema does not specify the type for, convert them into - /// the most specific type based on the value. - /// - public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiSchema schema) - { - if (openApiAny is OpenApiArray openApiArray) - { - var newArray = new OpenApiArray(); - foreach (var element in openApiArray) + // More narrow type detection for explicit strings, only check types that are passed as strings + if (schema == null) { - newArray.Add(GetSpecificOpenApiAny(element, schema?.Items)); + if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue)) + { + return new OpenApiDateTime(dateTimeValue); + } } + else if (type == "string") + { + if (format == "byte") + { + try + { + return new OpenApiByte(Convert.FromBase64String(value)); + } + catch (FormatException) + { } + } - return newArray; - } + if (format == "binary") + { + try + { + return new OpenApiBinary(Encoding.UTF8.GetBytes(value)); + } + catch (EncoderFallbackException) + { } + } - if (openApiAny is OpenApiObject openApiObject) - { - var newObject = new OpenApiObject(); + if (format == "date") + { + if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateValue)) + { + return new OpenApiDate(dateValue.Date); + } + } - foreach (var key in openApiObject.Keys.ToList()) - { - if ( schema != null && schema.Properties != null && schema.Properties.ContainsKey(key) ) + if (format == "date-time") { - newObject[key] = GetSpecificOpenApiAny(openApiObject[key], schema.Properties[key]); + if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue)) + { + return new OpenApiDateTime(dateTimeValue); + } } - else + + if (format == "password") { - newObject[key] = GetSpecificOpenApiAny(openApiObject[key], schema?.AdditionalProperties); + return new OpenApiPassword(value); } } - return newObject; - } - - if (!(openApiAny is OpenApiString)) - { return openApiAny; } - if (schema?.Type == null) - { - return GetSpecificOpenApiAny(openApiAny); - } - - var type = schema.Type; - var format = schema.Format; - - var value = ((OpenApiString)openApiAny).Value; - if (value == null || value == "null") { return new OpenApiNull(); } - if (type == "integer" && format == "int32") + if (schema?.Type == null) { - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) + if (value == "true") { - return new OpenApiInteger(intValue); + return new OpenApiBoolean(true); } - } - if (type == "integer" && format == "int64") - { - if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue)) + if (value == "false") { - return new OpenApiLong(longValue); + return new OpenApiBoolean(false); } - } - if (type == "integer") - { if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) { return new OpenApiInteger(intValue); } - } - if (type == "number" && format == "float") - { - if (float.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var floatValue)) + if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue)) { - return new OpenApiFloat(floatValue); + return new OpenApiLong(longValue); } - } - if (type == "number" && format == "double" ) - { if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue)) { return new OpenApiDouble(doubleValue); } - } - if (type == "number") - { - if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue)) + if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue)) { - return new OpenApiDouble(doubleValue); + return new OpenApiDateTime(dateTimeValue); } } - - if (type == "string" && format == "byte") + else { - try + if (type == "integer" && format == "int32") { - return new OpenApiByte(Convert.FromBase64String(value)); + if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) + { + return new OpenApiInteger(intValue); + } } - catch(FormatException) - { } - } - // binary - if (type == "string" && format == "binary") - { - try + if (type == "integer" && format == "int64") { - return new OpenApiBinary(Encoding.UTF8.GetBytes(value)); + if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue)) + { + return new OpenApiLong(longValue); + } } - catch(EncoderFallbackException) - { } - } - if (type == "string" && format == "date") - { - if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateValue)) + if (type == "integer") { - return new OpenApiDate(dateValue.Date); + if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue)) + { + return new OpenApiInteger(intValue); + } } - } - if (type == "string" && format == "date-time") - { - if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue)) + if (type == "number" && format == "float") { - return new OpenApiDateTime(dateTimeValue); + if (float.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var floatValue)) + { + return new OpenApiFloat(floatValue); + } } - } - if (type == "string" && format == "password") - { - return new OpenApiPassword(value); - } + if (type == "number" && format == "double") + { + if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue)) + { + return new OpenApiDouble(doubleValue); + } + } - if (type == "string") - { - return new OpenApiString(value); - } + if (type == "number") + { + if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue)) + { + return new OpenApiDouble(doubleValue); + } + } - if (type == "boolean") - { - if (bool.TryParse(value, out var booleanValue)) + if (type == "string" && format == "byte") + { + try + { + return new OpenApiByte(Convert.FromBase64String(value)); + } + catch (FormatException) + { } + } + + // binary + if (type == "string" && format == "binary") { - return new OpenApiBoolean(booleanValue); + try + { + return new OpenApiBinary(Encoding.UTF8.GetBytes(value)); + } + catch (EncoderFallbackException) + { } + } + + if (type == "string" && format == "date") + { + if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateValue)) + { + return new OpenApiDate(dateValue.Date); + } + } + + if (type == "string" && format == "date-time") + { + if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue)) + { + return new OpenApiDateTime(dateTimeValue); + } + } + + if (type == "string" && format == "password") + { + return new OpenApiPassword(value); + } + + if (type == "string") + { + return openApiAny; + } + + if (type == "boolean") + { + if (bool.TryParse(value, out var booleanValue)) + { + return new OpenApiBoolean(booleanValue); + } } } // If data conflicts with the given type, return a string. // This converter is used in the parser, so it does not perform any validations, // but the validator can be used to validate whether the data and given type conflicts. - return new OpenApiString(value); + return openApiAny; } } } diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs index abeee3d26..25f0eabc1 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ParseNode.cs @@ -15,16 +15,13 @@ namespace Microsoft.OpenApi.Readers.ParseNodes { internal abstract class ParseNode { - protected ParseNode(ParsingContext parsingContext, OpenApiDiagnostic diagnostic) + protected ParseNode(ParsingContext parsingContext) { Context = parsingContext; - Diagnostic = diagnostic; } public ParsingContext Context { get; } - public OpenApiDiagnostic Diagnostic { get; } - public MapNode CheckMapNode(string nodeName) { if (!(this is MapNode mapNode)) @@ -35,20 +32,20 @@ public MapNode CheckMapNode(string nodeName) return mapNode; } - public static ParseNode Create(ParsingContext context, OpenApiDiagnostic diagnostic, YamlNode node) + public static ParseNode Create(ParsingContext context, YamlNode node) { if (node is YamlSequenceNode listNode) { - return new ListNode(context, diagnostic, listNode); + return new ListNode(context, listNode); } if (node is YamlMappingNode mapNode) { - return new MapNode(context, diagnostic, mapNode); + return new MapNode(context, mapNode); } - return new ValueNode(context, diagnostic, node as YamlScalarNode); + return new ValueNode(context, node as YamlScalarNode); } public virtual List CreateList(Func map) @@ -100,4 +97,4 @@ public virtual List CreateListOfAny() } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/PatternFieldMap.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/PatternFieldMap.cs index 8dadfcee2..bbd153688 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/PatternFieldMap.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/PatternFieldMap.cs @@ -9,4 +9,4 @@ namespace Microsoft.OpenApi.Readers.ParseNodes internal class PatternFieldMap : Dictionary, Action> { } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs index 39b9370f8..2dd2c7e8a 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/PropertyNode.cs @@ -14,12 +14,11 @@ namespace Microsoft.OpenApi.Readers.ParseNodes { internal class PropertyNode : ParseNode { - public PropertyNode(ParsingContext context, OpenApiDiagnostic diagnostic, string name, YamlNode node) : base( - context, - diagnostic) + public PropertyNode(ParsingContext context, string name, YamlNode node) : base( + context) { Name = name; - Value = Create(context, diagnostic, node); + Value = Create(context, node); } public string Name { get; set; } @@ -43,12 +42,12 @@ public void ParseField( } catch (OpenApiReaderException ex) { - Diagnostic.Errors.Add(new OpenApiError(ex)); + Context.Diagnostic.Errors.Add(new OpenApiError(ex)); } catch (OpenApiException ex) { ex.Pointer = Context.GetLocation(); - Diagnostic.Errors.Add(new OpenApiError(ex)); + Context.Diagnostic.Errors.Add(new OpenApiError(ex)); } finally { @@ -67,12 +66,12 @@ public void ParseField( } catch (OpenApiReaderException ex) { - Diagnostic.Errors.Add(new OpenApiError(ex)); + Context.Diagnostic.Errors.Add(new OpenApiError(ex)); } catch (OpenApiException ex) { ex.Pointer = Context.GetLocation(); - Diagnostic.Errors.Add(new OpenApiError(ex)); + Context.Diagnostic.Errors.Add(new OpenApiError(ex)); } finally { @@ -81,7 +80,7 @@ public void ParseField( } else { - Diagnostic.Errors.Add( + Context.Diagnostic.Errors.Add( new OpenApiError("", $"{Name} is not a valid property at {Context.GetLocation()}")); } } @@ -92,4 +91,4 @@ public override IOpenApiAny CreateAny() throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/RootNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/RootNode.cs index 014e2e71f..42909bee6 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/RootNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/RootNode.cs @@ -14,8 +14,7 @@ internal class RootNode : ParseNode public RootNode( ParsingContext context, - OpenApiDiagnostic diagnostic, - YamlDocument yamlDocument) : base(context, diagnostic) + YamlDocument yamlDocument) : base(context) { _yamlDocument = yamlDocument; } @@ -28,12 +27,12 @@ public ParseNode Find(JsonPointer referencePointer) return null; } - return Create(Context, Diagnostic, yamlNode); + return Create(Context, yamlNode); } public MapNode GetMap() { - return new MapNode(Context, Diagnostic, (YamlMappingNode)_yamlDocument.RootNode); + return new MapNode(Context, (YamlMappingNode)_yamlDocument.RootNode); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs index b7669b538..68f4bd7ea 100644 --- a/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs +++ b/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs @@ -1,10 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -using System; -using System.Globalization; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Readers.Exceptions; +using SharpYaml; using SharpYaml.Serialization; namespace Microsoft.OpenApi.Readers.ParseNodes @@ -13,9 +12,8 @@ internal class ValueNode : ParseNode { private readonly YamlScalarNode _node; - public ValueNode(ParsingContext context, OpenApiDiagnostic diagnostic, YamlNode node) : base( - context, - diagnostic) + public ValueNode(ParsingContext context, YamlNode node) : base( + context) { if (!(node is YamlScalarNode scalarNode)) { @@ -36,7 +34,7 @@ public override string GetScalarValue() public override IOpenApiAny CreateAny() { var value = GetScalarValue(); - return new OpenApiString(value); + return new OpenApiString(value, this._node.Style == ScalarStyle.SingleQuoted || this._node.Style == ScalarStyle.DoubleQuoted || this._node.Style == ScalarStyle.Literal || this._node.Style == ScalarStyle.Folded); } } } diff --git a/src/Microsoft.OpenApi.Readers/ParsingContext.cs b/src/Microsoft.OpenApi.Readers/ParsingContext.cs index e88e00423..9c7277136 100644 --- a/src/Microsoft.OpenApi.Readers/ParsingContext.cs +++ b/src/Microsoft.OpenApi.Readers/ParsingContext.cs @@ -24,22 +24,34 @@ public class ParsingContext private readonly Stack _currentLocation = new Stack(); private readonly Dictionary _tempStorage = new Dictionary(); private readonly Dictionary> _scopedTempStorage = new Dictionary>(); - private IOpenApiVersionService _versionService; private readonly Dictionary> _loopStacks = new Dictionary>(); internal Dictionary> ExtensionParsers { get; set; } = new Dictionary>(); internal RootNode RootNode { get; set; } internal List Tags { get; private set; } = new List(); internal Uri BaseUrl { get; set; } + /// + /// Diagnostic object that returns metadata about the parsing process. + /// + public OpenApiDiagnostic Diagnostic { get; } + + /// + /// Create Parsing Context + /// + /// Provide instance for diagnotic object for collecting and accessing information about the parsing. + public ParsingContext(OpenApiDiagnostic diagnostic) + { + Diagnostic = diagnostic; + } + /// /// Initiates the parsing process. Not thread safe and should only be called once on a parsing context /// /// Yaml document to parse. - /// Diagnostic object which will return diagnostic results of the operation. /// An OpenApiDocument populated based on the passed yamlDocument - internal OpenApiDocument Parse(YamlDocument yamlDocument, OpenApiDiagnostic diagnostic) + internal OpenApiDocument Parse(YamlDocument yamlDocument) { - RootNode = new RootNode(this, diagnostic, yamlDocument); + RootNode = new RootNode(this, yamlDocument); var inputVersion = GetVersion(RootNode); @@ -50,13 +62,13 @@ internal OpenApiDocument Parse(YamlDocument yamlDocument, OpenApiDiagnostic diag case string version when version == "2.0": VersionService = new OpenApiV2VersionService(); doc = VersionService.LoadDocument(RootNode); - diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi2_0; + this.Diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi2_0; break; case string version when version.StartsWith("3.0"): VersionService = new OpenApiV3VersionService(); doc = VersionService.LoadDocument(RootNode); - diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi3_0; + this.Diagnostic.SpecificationVersion = OpenApiSpecVersion.OpenApi3_0; break; default: @@ -71,11 +83,10 @@ internal OpenApiDocument Parse(YamlDocument yamlDocument, OpenApiDiagnostic diag /// /// /// OpenAPI version of the fragment - /// Diagnostic object which will return diagnostic results of the operation. /// An OpenApiDocument populated based on the passed yamlDocument - internal T ParseFragment(YamlDocument yamlDocument, OpenApiSpecVersion version, OpenApiDiagnostic diagnostic) where T : IOpenApiElement + internal T ParseFragment(YamlDocument yamlDocument, OpenApiSpecVersion version) where T : IOpenApiElement { - var node = ParseNode.Create(this, diagnostic, yamlDocument.RootNode); + var node = ParseNode.Create(this, yamlDocument.RootNode); T element = default(T); @@ -115,17 +126,7 @@ private static string GetVersion(RootNode rootNode) /// /// Service providing all Version specific conversion functions /// - internal IOpenApiVersionService VersionService - { - get - { - return _versionService; - } - set - { - _versionService = value; - } - } + internal IOpenApiVersionService VersionService { get; set; } /// /// End the current object. @@ -146,7 +147,7 @@ public string GetLocation() /// /// Gets the value from the temporary storage matching the given key. /// - public T GetFromTempStorage(string key, object scope = null) where T : class + public T GetFromTempStorage(string key, object scope = null) { Dictionary storage; @@ -156,10 +157,10 @@ public T GetFromTempStorage(string key, object scope = null) where T : class } else if (!_scopedTempStorage.TryGetValue(scope, out storage)) { - return null; + return default(T); } - return storage.TryGetValue(key, out var value) ? (T)value : null; + return storage.TryGetValue(key, out var value) ? (T)value : default(T); } /// @@ -244,4 +245,4 @@ public void PopLoop(string loopid) } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/Properties/AssemblyInfo.cs b/src/Microsoft.OpenApi.Readers/Properties/AssemblyInfo.cs index 1d8941520..b42e91bd1 100644 --- a/src/Microsoft.OpenApi.Readers/Properties/AssemblyInfo.cs +++ b/src/Microsoft.OpenApi.Readers/Properties/AssemblyInfo.cs @@ -5,4 +5,4 @@ [assembly: InternalsVisibleTo( - "Microsoft.OpenApi.Readers.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] \ No newline at end of file + "Microsoft.OpenApi.Readers.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] diff --git a/src/Microsoft.OpenApi.Readers/Services/OpenApiReferenceResolver.cs b/src/Microsoft.OpenApi.Readers/Services/OpenApiReferenceResolver.cs index 60a9bbfb6..c6ebe24b3 100644 --- a/src/Microsoft.OpenApi.Readers/Services/OpenApiReferenceResolver.cs +++ b/src/Microsoft.OpenApi.Readers/Services/OpenApiReferenceResolver.cs @@ -20,7 +20,7 @@ internal class OpenApiReferenceResolver : OpenApiVisitorBase private bool _resolveRemoteReferences; private List _errors = new List(); - public OpenApiReferenceResolver(OpenApiDocument currentDocument, bool resolveRemoteReferences = true) + public OpenApiReferenceResolver(OpenApiDocument currentDocument, bool resolveRemoteReferences = true) { _currentDocument = currentDocument; _resolveRemoteReferences = resolveRemoteReferences; @@ -66,7 +66,7 @@ public override void Visit(OpenApiOperation operation) { ResolveObject(operation.RequestBody, r => operation.RequestBody = r); ResolveList(operation.Parameters); - + if (operation.Tags != null) { ResolveTags(operation.Tags); @@ -128,6 +128,16 @@ public override void Visit(IList parameters) ResolveList(parameters); } + /// + /// Resolve all references used in a parameter + /// + public override void Visit(OpenApiParameter parameter) + { + ResolveObject(parameter.Schema, r => parameter.Schema = r); + ResolveMap(parameter.Examples); + } + + /// /// Resolve all references to links /// diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs index 2469c887f..99bc4451a 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiContactDeserializer.cs @@ -51,4 +51,4 @@ public static OpenApiContact LoadContact(ParseNode node) return contact; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs index debd3cfcd..6302eaf84 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs @@ -137,7 +137,7 @@ private static void MakeServers(IList servers, ParsingContext con //Validate host if (host != null && !IsHostValid(host)) { - rootNode.Diagnostic.Errors.Add(new OpenApiError(rootNode.Context.GetLocation(), "Invalid host")); + rootNode.Context.Diagnostic.Errors.Add(new OpenApiError(rootNode.Context.GetLocation(), "Invalid host")); return; } @@ -324,4 +324,4 @@ public override void Visit(OpenApiOperation operation) } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiExternalDocsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiExternalDocsDeserializer.cs index b0d2df871..4d57e7b3f 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiExternalDocsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiExternalDocsDeserializer.cs @@ -44,4 +44,4 @@ public static OpenApiExternalDocs LoadExternalDocs(ParseNode node) return externalDocs; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs index 3ebdb0e1d..32caf86aa 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs @@ -199,4 +199,4 @@ private static void LoadStyle(OpenApiHeader header, string style) } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs index b2a3083f7..5854672d3 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiInfoDeserializer.cs @@ -71,4 +71,4 @@ public static OpenApiInfo LoadInfo(ParseNode node) return info; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs index 567d37de1..4c4009f57 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiLicenseDeserializer.cs @@ -46,4 +46,4 @@ public static OpenApiLicense LoadLicense(ParseNode node) return license; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs index ca4da470f..785ed6ee7 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiOperationDeserializer.cs @@ -23,7 +23,6 @@ internal static partial class OpenApiV2Deserializer valueNode => LoadTagByReference( valueNode.Context, - valueNode.Diagnostic, valueNode.GetScalarValue())) }, { @@ -210,7 +209,6 @@ internal static OpenApiRequestBody CreateRequestBody( private static OpenApiTag LoadTagByReference( ParsingContext context, - OpenApiDiagnostic diagnostic, string tagName) { var tagObject = new OpenApiTag() @@ -222,4 +220,4 @@ private static OpenApiTag LoadTagByReference( return tagObject; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs index 601003862..5be08c71e 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs @@ -302,4 +302,4 @@ public static OpenApiParameter LoadParameter(ParseNode node, bool loadRequestBod return parameter; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs index 5b3a782e9..d8ff7b961 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiPathItemDeserializer.cs @@ -53,4 +53,4 @@ public static OpenApiPathItem LoadPathItem(ParseNode node) return pathItem; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs index 5b8a1a24a..2aa5de979 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiPathsDeserializer.cs @@ -32,4 +32,4 @@ public static OpenApiPaths LoadPaths(ParseNode node) return domainObject; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs index c28508848..343dcd2ce 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiResponseDeserializer.cs @@ -62,14 +62,18 @@ internal static partial class OpenApiV2Deserializer private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, ParsingContext context) { - var produces = context.GetFromTempStorage>(TempStorageKeys.OperationProduces) ?? - context.GetFromTempStorage>(TempStorageKeys.GlobalProduces); - if (response.Content == null) { response.Content = new Dictionary(); } + else if (context.GetFromTempStorage(TempStorageKeys.ResponseProducesSet, response)) + { + // Process "produces" only once since once specified at operation level it cannot be overriden. + return; + } + var produces = context.GetFromTempStorage>(TempStorageKeys.OperationProduces) + ?? context.GetFromTempStorage>(TempStorageKeys.GlobalProduces); if (produces != null) { foreach (var produce in produces) @@ -96,6 +100,7 @@ private static void ProcessProduces(MapNode mapNode, OpenApiResponse response, P } context.SetTempStorage(TempStorageKeys.ResponseSchema, null, response); + context.SetTempStorage(TempStorageKeys.ResponseProducesSet, true, response); } } @@ -124,7 +129,10 @@ private static void LoadExample(OpenApiResponse response, string mediaType, Pars } else { - mediaTypeObject = new OpenApiMediaType(); + mediaTypeObject = new OpenApiMediaType + { + Schema = node.Context.GetFromTempStorage(TempStorageKeys.ResponseSchema, response) + }; response.Content.Add(mediaType, mediaTypeObject); } @@ -158,4 +166,4 @@ public static OpenApiResponse LoadResponse(ParseNode node) return response; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs index 195965cbd..41c40e497 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs @@ -214,15 +214,15 @@ internal static partial class OpenApiV2Deserializer { OpenApiConstants.Default, new AnyFieldMapParameter( - s => s.Default, - (s, v) => s.Default = v, + s => s.Default, + (s, v) => s.Default = v, s => s) }, { OpenApiConstants.Example, new AnyFieldMapParameter( - s => s.Example, - (s, v) => s.Example = v, + s => s.Example, + (s, v) => s.Example = v, s => s) } }; @@ -231,8 +231,8 @@ internal static partial class OpenApiV2Deserializer { OpenApiConstants.Enum, new AnyListFieldMapParameter( - s => s.Enum, - (s, v) => s.Enum = v, + s => s.Enum, + (s, v) => s.Enum = v, s => s) } }; @@ -260,4 +260,4 @@ public static OpenApiSchema LoadSchema(ParseNode node) return schema; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiSecurityRequirementDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiSecurityRequirementDeserializer.cs index d214a9b11..c5e0776ee 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiSecurityRequirementDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiSecurityRequirementDeserializer.cs @@ -22,7 +22,6 @@ public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) { var scheme = LoadSecuritySchemeByReference( mapNode.Context, - mapNode.Diagnostic, property.Name); var scopes = property.Value.CreateSimpleList(n2 => n2.GetScalarValue()); @@ -33,8 +32,8 @@ public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) } else { - node.Diagnostic.Errors.Add( - new OpenApiError(node.Context.GetLocation(), + mapNode.Context.Diagnostic.Errors.Add( + new OpenApiError(node.Context.GetLocation(), $"Scheme {property.Name} is not found")); } } @@ -44,7 +43,6 @@ public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) private static OpenApiSecurityScheme LoadSecuritySchemeByReference( ParsingContext context, - OpenApiDiagnostic diagnostic, string schemeName) { var securitySchemeObject = new OpenApiSecurityScheme() @@ -60,4 +58,4 @@ private static OpenApiSecurityScheme LoadSecuritySchemeByReference( return securitySchemeObject; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiSecuritySchemeDeserializer.cs index 117d1f3c4..7e0c6c1dc 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiSecuritySchemeDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiSecuritySchemeDeserializer.cs @@ -128,4 +128,4 @@ public static OpenApiSecurityScheme LoadSecurityScheme(ParseNode node) return securityScheme; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiTagDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiTagDeserializer.cs index 380999475..12fae8660 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiTagDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiTagDeserializer.cs @@ -54,4 +54,4 @@ public static OpenApiTag LoadTag(ParseNode n) return domainObject; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs index ace68921b..4bb15a8d9 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2Deserializer.cs @@ -56,7 +56,7 @@ private static void ProcessAnyFields( catch (OpenApiException exception) { exception.Pointer = mapNode.Context.GetLocation(); - mapNode.Diagnostic.Errors.Add(new OpenApiError(exception)); + mapNode.Context.Diagnostic.Errors.Add(new OpenApiError(exception)); } finally { @@ -95,7 +95,7 @@ private static void ProcessAnyListFields( catch (OpenApiException exception) { exception.Pointer = mapNode.Context.GetLocation(); - mapNode.Diagnostic.Errors.Add(new OpenApiError(exception)); + mapNode.Context.Diagnostic.Errors.Add(new OpenApiError(exception)); } finally { @@ -136,7 +136,7 @@ private static void ProcessAnyMapFields( catch (OpenApiException exception) { exception.Pointer = mapNode.Context.GetLocation(); - mapNode.Diagnostic.Errors.Add(new OpenApiError(exception)); + mapNode.Context.Diagnostic.Errors.Add(new OpenApiError(exception)); } finally { @@ -169,4 +169,4 @@ private static string LoadString(ParseNode node) return node.GetScalarValue(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs index e9561b367..99dfcd225 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiV2VersionService.cs @@ -59,7 +59,7 @@ private static OpenApiReference ParseLocalReference(string localReference) var id = localReference.Substring( segments[0].Length + "/".Length + segments[1].Length + "/".Length); - return new OpenApiReference {Type = referenceType, Id = id}; + return new OpenApiReference { Type = referenceType, Id = id }; } throw new OpenApiException( @@ -179,4 +179,4 @@ public T LoadElement(ParseNode node) where T : IOpenApiElement return (T)_loaders[typeof(T)](node); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs index 6e2273839..ac7db2db6 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiXmlDeserializer.cs @@ -76,4 +76,4 @@ public static OpenApiXml LoadXml(ParseNode node) return xml; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs b/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs index 4e0e890a9..5a216e086 100644 --- a/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs +++ b/src/Microsoft.OpenApi.Readers/V2/TempStorageKeys.cs @@ -12,9 +12,10 @@ internal static class TempStorageKeys public const string BodyParameter = "bodyParameter"; public const string FormParameters = "formParameters"; public const string OperationProduces = "operationProduces"; + public const string ResponseProducesSet = "responseProducesSet"; public const string OperationConsumes = "operationConsumes"; public const string GlobalConsumes = "globalConsumes"; public const string GlobalProduces = "globalProduces"; public const string ParameterIsBodyOrFormData = "parameterIsBodyOrFormData"; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiCallbackDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiCallbackDeserializer.cs index 60d69b9e5..af1376580 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiCallbackDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiCallbackDeserializer.cs @@ -41,4 +41,4 @@ public static OpenApiCallback LoadCallback(ParseNode node) return domainObject; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs index febeda730..30d711d33 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiComponentsDeserializer.cs @@ -47,4 +47,4 @@ public static OpenApiComponents LoadComponents(ParseNode node) return components; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs index 154ed23c0..151a12354 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiContactDeserializer.cs @@ -51,4 +51,4 @@ public static OpenApiContact LoadContact(ParseNode node) return contact; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiDiscriminatorDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiDiscriminatorDeserializer.cs index 8978e4238..867057d32 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiDiscriminatorDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiDiscriminatorDeserializer.cs @@ -45,4 +45,4 @@ public static OpenApiDiscriminator LoadDiscriminator(ParseNode node) return discriminator; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs index 2221584f1..df1434cd9 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiDocumentDeserializer.cs @@ -58,4 +58,4 @@ public static OpenApiDocument LoadOpenApi(RootNode rootNode) return openApidoc; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs index 18f1154c3..b3bda4b61 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiEncodingDeserializer.cs @@ -75,4 +75,4 @@ public static OpenApiEncoding LoadEncoding(ParseNode node) return encoding; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs index 36f591a9a..1e114ad73 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiExampleDeserializer.cs @@ -67,4 +67,4 @@ public static OpenApiExample LoadExample(ParseNode node) return example; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiExternalDocsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiExternalDocsDeserializer.cs index ec2c16ff1..2fef79b6a 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiExternalDocsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiExternalDocsDeserializer.cs @@ -45,4 +45,4 @@ public static OpenApiExternalDocs LoadExternalDocs(ParseNode node) return externalDocs; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs index 86e86237e..1616d67f0 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiHeaderDeserializer.cs @@ -101,4 +101,4 @@ public static OpenApiHeader LoadHeader(ParseNode node) return header; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs index 17a97c117..d5de92852 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiInfoDeserializer.cs @@ -65,11 +65,11 @@ public static OpenApiInfo LoadInfo(ParseNode node) var mapNode = node.CheckMapNode("Info"); var info = new OpenApiInfo(); - var required = new List {"title", "version"}; + var required = new List { "title", "version" }; ParseMap(mapNode, info, InfoFixedFields, InfoPatternFields); return info; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs index 6bd352f53..3c38d8b9a 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiLicenseDeserializer.cs @@ -46,4 +46,4 @@ internal static OpenApiLicense LoadLicense(ParseNode node) return license; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs index 06ec8de3e..7bf4c650b 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiLinkDeserializer.cs @@ -69,4 +69,4 @@ public static OpenApiLink LoadLink(ParseNode node) return link; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs index 52e99a8f9..695f1cc1b 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiMediaTypeDeserializer.cs @@ -64,7 +64,7 @@ internal static partial class OpenApiV3Deserializer }; - private static readonly AnyMapFieldMap _mediaTypeAnyMapOpenApiExampleFields = + private static readonly AnyMapFieldMap _mediaTypeAnyMapOpenApiExampleFields = new AnyMapFieldMap { { @@ -96,4 +96,4 @@ public static OpenApiMediaType LoadMediaType(ParseNode node) return mediaType; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowDeserializer.cs index adc814f33..2653ce631 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowDeserializer.cs @@ -57,4 +57,4 @@ public static OpenApiOAuthFlow LoadOAuthFlow(ParseNode node) return oauthFlow; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowsDeserializer.cs index 022ed35fd..bd19f2716 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiOAuthFlowsDeserializer.cs @@ -41,4 +41,4 @@ public static OpenApiOAuthFlows LoadOAuthFlows(ParseNode node) return oAuthFlows; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiOperationDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiOperationDeserializer.cs index 3ab39c828..16f7a16f4 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiOperationDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiOperationDeserializer.cs @@ -21,7 +21,6 @@ internal static partial class OpenApiV3Deserializer valueNode => LoadTagByReference( valueNode.Context, - valueNode.Diagnostic, valueNode.GetScalarValue())) }, { @@ -111,7 +110,6 @@ internal static OpenApiOperation LoadOperation(ParseNode node) private static OpenApiTag LoadTagByReference( ParsingContext context, - OpenApiDiagnostic diagnostic, string tagName) { var tagObject = new OpenApiTag() @@ -123,8 +121,8 @@ private static OpenApiTag LoadTagByReference( Id = tagName } }; - + return tagObject; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs index 61dbbc7fa..e8fad07a5 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiParameterDeserializer.cs @@ -158,4 +158,4 @@ public static OpenApiParameter LoadParameter(ParseNode node) return parameter; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs index ff08ce186..70f53b83a 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathItemDeserializer.cs @@ -57,4 +57,4 @@ public static OpenApiPathItem LoadPathItem(ParseNode node) return pathItem; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs index 2020628d0..fcfad096c 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiPathsDeserializer.cs @@ -32,4 +32,4 @@ public static OpenApiPaths LoadPaths(ParseNode node) return domainObject; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs index efd54b101..a2633028e 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiRequestBodyDeserializer.cs @@ -61,4 +61,4 @@ public static OpenApiRequestBody LoadRequestBody(ParseNode node) return requestBody; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs index 30ea4a52e..70ea0c9bf 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiResponseDeserializer.cs @@ -58,11 +58,11 @@ public static OpenApiResponse LoadResponse(ParseNode node) return mapNode.GetReferencedObject(ReferenceType.Response, pointer); } - var requiredFields = new List {"description"}; + var requiredFields = new List { "description" }; var response = new OpenApiResponse(); ParseMap(mapNode, response, _responseFixedFields, _responsePatternFields); return response; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs index 580d3ff67..9fe4d075f 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiResponsesDeserializer.cs @@ -32,4 +32,4 @@ public static OpenApiResponses LoadResponses(ParseNode node) return domainObject; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs index db1380bd8..87769c7c9 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs @@ -255,8 +255,8 @@ internal static partial class OpenApiV3Deserializer { OpenApiConstants.Example, new AnyFieldMapParameter( - s => s.Example, - (s, v) => s.Example = v, + s => s.Example, + (s, v) => s.Example = v, s => s) } }; @@ -266,7 +266,7 @@ internal static partial class OpenApiV3Deserializer { OpenApiConstants.Enum, new AnyListFieldMapParameter( - s => s.Enum, + s => s.Enum, (s, v) => s.Enum = v, s => s) } @@ -280,11 +280,11 @@ public static OpenApiSchema LoadSchema(ParseNode node) if (pointer != null) { - return new OpenApiSchema() - { - UnresolvedReference = true, - Reference = node.Context.VersionService.ConvertToOpenApiReference(pointer,ReferenceType.Schema) - }; + return new OpenApiSchema() + { + UnresolvedReference = true, + Reference = node.Context.VersionService.ConvertToOpenApiReference(pointer, ReferenceType.Schema) + }; } var schema = new OpenApiSchema(); @@ -300,4 +300,4 @@ public static OpenApiSchema LoadSchema(ParseNode node) return schema; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiSecurityRequirementDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiSecurityRequirementDeserializer.cs index b8c3c38bb..b6b80cf7b 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiSecurityRequirementDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiSecurityRequirementDeserializer.cs @@ -22,7 +22,6 @@ public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) { var scheme = LoadSecuritySchemeByReference( mapNode.Context, - mapNode.Diagnostic, property.Name); var scopes = property.Value.CreateSimpleList(value => value.GetScalarValue()); @@ -33,7 +32,7 @@ public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) } else { - node.Diagnostic.Errors.Add( + mapNode.Context.Diagnostic.Errors.Add( new OpenApiError(node.Context.GetLocation(), $"Scheme {property.Name} is not found")); } } @@ -43,7 +42,6 @@ public static OpenApiSecurityRequirement LoadSecurityRequirement(ParseNode node) private static OpenApiSecurityScheme LoadSecuritySchemeByReference( ParsingContext context, - OpenApiDiagnostic diagnostic, string schemeName) { var securitySchemeObject = new OpenApiSecurityScheme() @@ -59,4 +57,4 @@ private static OpenApiSecurityScheme LoadSecuritySchemeByReference( return securitySchemeObject; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiSecuritySchemeDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiSecuritySchemeDeserializer.cs index 8657faceb..0e7b1c39c 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiSecuritySchemeDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiSecuritySchemeDeserializer.cs @@ -86,4 +86,4 @@ public static OpenApiSecurityScheme LoadSecurityScheme(ParseNode node) return securityScheme; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiServerDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiServerDeserializer.cs index 39842c974..e278abc90 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiServerDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiServerDeserializer.cs @@ -51,4 +51,4 @@ public static OpenApiServer LoadServer(ParseNode node) return server; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiServerVariableDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiServerVariableDeserializer.cs index 9fc955a78..9b6acbc8d 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiServerVariableDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiServerVariableDeserializer.cs @@ -53,4 +53,4 @@ public static OpenApiServerVariable LoadServerVariable(ParseNode node) return serverVariable; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiTagDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiTagDeserializer.cs index 1f3a36afb..6a987969b 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiTagDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiTagDeserializer.cs @@ -54,4 +54,4 @@ public static OpenApiTag LoadTag(ParseNode n) return domainObject; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs index 5d81b66f2..9689c8fe1 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3Deserializer.cs @@ -56,7 +56,7 @@ private static void ProcessAnyFields( catch (OpenApiException exception) { exception.Pointer = mapNode.Context.GetLocation(); - mapNode.Diagnostic.Errors.Add(new OpenApiError(exception)); + mapNode.Context.Diagnostic.Errors.Add(new OpenApiError(exception)); } finally { @@ -91,7 +91,7 @@ private static void ProcessAnyListFields( catch (OpenApiException exception) { exception.Pointer = mapNode.Context.GetLocation(); - mapNode.Diagnostic.Errors.Add(new OpenApiError(exception)); + mapNode.Context.Diagnostic.Errors.Add(new OpenApiError(exception)); } finally { @@ -132,7 +132,7 @@ private static void ProcessAnyMapFields( catch (OpenApiException exception) { exception.Pointer = mapNode.Context.GetLocation(); - mapNode.Diagnostic.Errors.Add(new OpenApiError(exception)); + mapNode.Context.Diagnostic.Errors.Add(new OpenApiError(exception)); } finally { @@ -189,4 +189,4 @@ private static string LoadString(ParseNode node) return node.GetScalarValue(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs index 891822a21..1630e60e1 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiV3VersionService.cs @@ -19,7 +19,8 @@ namespace Microsoft.OpenApi.Readers.V3 /// internal class OpenApiV3VersionService : IOpenApiVersionService { - private IDictionary> _loaders = new Dictionary> { + private IDictionary> _loaders = new Dictionary> + { [typeof(IOpenApiAny)] = OpenApiV3Deserializer.LoadAny, [typeof(OpenApiCallback)] = OpenApiV3Deserializer.LoadCallback, [typeof(OpenApiComponents)] = OpenApiV3Deserializer.LoadComponents, @@ -48,7 +49,7 @@ internal class OpenApiV3VersionService : IOpenApiVersionService [typeof(OpenApiTag)] = OpenApiV3Deserializer.LoadTag, [typeof(OpenApiXml)] = OpenApiV3Deserializer.LoadXml }; - + /// /// Parse the string to a object. /// @@ -108,7 +109,7 @@ public OpenApiDocument LoadDocument(RootNode rootNode) public T LoadElement(ParseNode node) where T : IOpenApiElement { - return (T)_loaders[typeof(T)](node); + return (T)_loaders[typeof(T)](node); } private OpenApiReference ParseLocalReference(string localReference) @@ -125,11 +126,11 @@ private OpenApiReference ParseLocalReference(string localReference) if (segments[1] == "components") { var referenceType = segments[2].GetEnumFromDisplayName(); - return new OpenApiReference {Type = referenceType, Id = segments[3]}; + return new OpenApiReference { Type = referenceType, Id = segments[3] }; } } throw new OpenApiException(string.Format(SRResource.ReferenceHasInvalidFormat, localReference)); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiXmlDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiXmlDeserializer.cs index 0b6196c65..dc05da1c2 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiXmlDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiXmlDeserializer.cs @@ -67,4 +67,4 @@ public static OpenApiXml LoadXml(ParseNode node) return xml; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Readers/YamlHelper.cs b/src/Microsoft.OpenApi.Readers/YamlHelper.cs index a283ea0d3..90794b080 100644 --- a/src/Microsoft.OpenApi.Readers/YamlHelper.cs +++ b/src/Microsoft.OpenApi.Readers/YamlHelper.cs @@ -31,4 +31,4 @@ public static YamlNode ParseYamlString(string yamlString) return yamlDocument.RootNode; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Tool/Microsoft.OpenApi.Tool.csproj b/src/Microsoft.OpenApi.Tool/Microsoft.OpenApi.Tool.csproj new file mode 100644 index 000000000..f8f1eab1c --- /dev/null +++ b/src/Microsoft.OpenApi.Tool/Microsoft.OpenApi.Tool.csproj @@ -0,0 +1,25 @@ + + + + Exe + netcoreapp3.1 + true + openapi + ./../../artifacts + 0.5.0 + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.OpenApi.Tool/OpenApiService.cs b/src/Microsoft.OpenApi.Tool/OpenApiService.cs new file mode 100644 index 000000000..7a8e8ced2 --- /dev/null +++ b/src/Microsoft.OpenApi.Tool/OpenApiService.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers; +using Microsoft.OpenApi.Validations; +using Microsoft.OpenApi.Writers; + +namespace Microsoft.OpenApi.Tool +{ + static class OpenApiService + { + public static void ProcessOpenApiDocument( + FileInfo input, + FileInfo output, + OpenApiSpecVersion version, + OpenApiFormat format, + bool inline) + { + OpenApiDocument document; + using (Stream stream = input.OpenRead()) + { + + document = new OpenApiStreamReader(new OpenApiReaderSettings + { + ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences, + RuleSet = ValidationRuleSet.GetDefaultRuleSet() + } + ).Read(stream, out var context); + if (context.Errors.Count != 0) + { + var errorReport = new StringBuilder(); + + foreach (var error in context.Errors) + { + errorReport.AppendLine(error.ToString()); + } + + throw new ArgumentException(String.Join(Environment.NewLine, context.Errors.Select(e => e.Message).ToArray())); + } + } + + using (var outputStream = output?.Create()) + { + TextWriter textWriter; + + if (outputStream!=null) + { + textWriter = new StreamWriter(outputStream); + } else + { + textWriter = Console.Out; + } + + var settings = new OpenApiWriterSettings() + { + ReferenceInline = inline == true ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences + }; + IOpenApiWriter writer; + switch (format) + { + case OpenApiFormat.Json: + writer = new OpenApiJsonWriter(textWriter, settings); + break; + case OpenApiFormat.Yaml: + writer = new OpenApiYamlWriter(textWriter, settings); + break; + default: + throw new ArgumentException("Unknown format"); + } + + document.Serialize(writer,version ); + + textWriter.Flush(); + } + } +} +} diff --git a/src/Microsoft.OpenApi.Tool/Program.cs b/src/Microsoft.OpenApi.Tool/Program.cs new file mode 100644 index 000000000..3d229c411 --- /dev/null +++ b/src/Microsoft.OpenApi.Tool/Program.cs @@ -0,0 +1,30 @@ +using System; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.IO; +using System.Threading.Tasks; +using Microsoft.OpenApi; + +namespace Microsoft.OpenApi.Tool +{ + class Program + { + static async Task Main(string[] args) + { + var command = new RootCommand + { + new Option("--input") { Argument = new Argument() }, + new Option("--output") { Argument = new Argument() }, + new Option("--version") { Argument = new Argument() }, + new Option("--format") { Argument = new Argument() }, + new Option("--inline") { Argument = new Argument() } + }; + + command.Handler = CommandHandler.Create( + OpenApiService.ProcessOpenApiDocument); + + // Parse the incoming args and invoke the handler + return await command.InvokeAsync(args); + } + } +} diff --git a/src/Microsoft.OpenApi.Workbench/App.xaml.cs b/src/Microsoft.OpenApi.Workbench/App.xaml.cs index 6b849bd1f..d3fc6dcb7 100644 --- a/src/Microsoft.OpenApi.Workbench/App.xaml.cs +++ b/src/Microsoft.OpenApi.Workbench/App.xaml.cs @@ -11,4 +11,4 @@ namespace Microsoft.OpenApi.Workbench public partial class App : Application { } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Workbench/MainModel.cs b/src/Microsoft.OpenApi.Workbench/MainModel.cs index 087340f14..6304b7f23 100644 --- a/src/Microsoft.OpenApi.Workbench/MainModel.cs +++ b/src/Microsoft.OpenApi.Workbench/MainModel.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Text; using Microsoft.OpenApi.Extensions; @@ -11,6 +12,7 @@ using Microsoft.OpenApi.Readers; using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Validations; +using Microsoft.OpenApi.Writers; namespace Microsoft.OpenApi.Workbench { @@ -31,6 +33,11 @@ public class MainModel : INotifyPropertyChanged private string _renderTime; + /// + /// Default format. + /// + private bool _Inline = false; + /// /// Default format. /// @@ -112,6 +119,16 @@ public OpenApiFormat Format } } + public bool Inline + { + get => _Inline; + set + { + _Inline = value; + OnPropertyChanged(nameof(Inline)); + } + } + public OpenApiSpecVersion Version { get => _version; @@ -176,16 +193,16 @@ internal void ParseDocument() { stream = CreateStream(_input); } - + var stopwatch = new Stopwatch(); stopwatch.Start(); var document = new OpenApiStreamReader(new OpenApiReaderSettings - { - ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences, - RuleSet = ValidationRuleSet.GetDefaultRuleSet() - } + { + ReferenceResolution = ReferenceResolutionSetting.ResolveLocalReferences, + RuleSet = ValidationRuleSet.GetDefaultRuleSet() + } ).Read(stream, out var context); stopwatch.Stop(); ParseTime = $"{stopwatch.ElapsedMilliseconds} ms"; @@ -217,7 +234,7 @@ internal void ParseDocument() var walker = new OpenApiWalker(statsVisitor); walker.Walk(document); - Errors += Environment.NewLine + "Statistics:" + Environment.NewLine + statsVisitor.GetStatisticsReport(); + Errors += Environment.NewLine + "Statistics:" + Environment.NewLine + statsVisitor.GetStatisticsReport(); } catch (Exception ex) { @@ -232,10 +249,14 @@ internal void ParseDocument() private string WriteContents(OpenApiDocument document) { var outputStream = new MemoryStream(); + document.Serialize( outputStream, Version, - Format); + Format, + new OpenApiWriterSettings() { + ReferenceInline = this.Inline == true ? ReferenceInlineSetting.InlineLocalReferences : ReferenceInlineSetting.DoNotInlineReferences + }); outputStream.Position = 0; @@ -259,4 +280,4 @@ private MemoryStream CreateStream(string text) /// public event PropertyChangedEventHandler PropertyChanged; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml index 21aba30e4..daf8a2209 100644 --- a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml +++ b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml @@ -42,6 +42,7 @@ + diff --git a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs index 214232d04..f33132359 100644 --- a/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs +++ b/src/Microsoft.OpenApi.Workbench/MainWindow.xaml.cs @@ -23,4 +23,4 @@ private void Button_Click(object sender, RoutedEventArgs e) _mainModel.ParseDocument(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi.Workbench/Properties/AssemblyInfo.cs b/src/Microsoft.OpenApi.Workbench/Properties/AssemblyInfo.cs index 6b1d07384..b9a41106f 100644 --- a/src/Microsoft.OpenApi.Workbench/Properties/AssemblyInfo.cs +++ b/src/Microsoft.OpenApi.Workbench/Properties/AssemblyInfo.cs @@ -33,11 +33,11 @@ [assembly: ThemeInfo( ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located - //(used if a resource is not found in the page, - // or application resource dictionaries) + //(used if a resource is not found in the page, + // or application resource dictionaries) ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located - //(used if a resource is not found in the page, - // app, or any theme specific resource dictionaries) + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) )] // Version information for an assembly consists of the following four values: @@ -51,4 +51,4 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Microsoft.OpenApi/Any/AnyType.cs b/src/Microsoft.OpenApi/Any/AnyType.cs index 12bbec6d4..d0addd808 100644 --- a/src/Microsoft.OpenApi/Any/AnyType.cs +++ b/src/Microsoft.OpenApi/Any/AnyType.cs @@ -28,4 +28,4 @@ public enum AnyType /// Object } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/IOpenApiAny.cs b/src/Microsoft.OpenApi/Any/IOpenApiAny.cs index c8e5f2dea..26c5f4d87 100644 --- a/src/Microsoft.OpenApi/Any/IOpenApiAny.cs +++ b/src/Microsoft.OpenApi/Any/IOpenApiAny.cs @@ -15,4 +15,4 @@ public interface IOpenApiAny : IOpenApiElement, IOpenApiExtension /// AnyType AnyType { get; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/IOpenApiPrimitive.cs b/src/Microsoft.OpenApi/Any/IOpenApiPrimitive.cs index 79dda83bd..0e286d1a4 100644 --- a/src/Microsoft.OpenApi/Any/IOpenApiPrimitive.cs +++ b/src/Microsoft.OpenApi/Any/IOpenApiPrimitive.cs @@ -74,4 +74,4 @@ public interface IOpenApiPrimitive : IOpenApiAny /// PrimitiveType PrimitiveType { get; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiArray.cs b/src/Microsoft.OpenApi/Any/OpenApiArray.cs index abd4ae099..5ef0087f2 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiArray.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiArray.cs @@ -34,4 +34,4 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiBinary.cs b/src/Microsoft.OpenApi/Any/OpenApiBinary.cs index d08493912..da1bedad8 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiBinary.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiBinary.cs @@ -22,4 +22,4 @@ public OpenApiBinary(byte[] value) /// public override PrimitiveType PrimitiveType { get; } = PrimitiveType.Binary; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiBoolean.cs b/src/Microsoft.OpenApi/Any/OpenApiBoolean.cs index a476bfe38..f531e0135 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiBoolean.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiBoolean.cs @@ -22,4 +22,4 @@ public OpenApiBoolean(bool value) /// public override PrimitiveType PrimitiveType { get; } = PrimitiveType.Boolean; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiByte.cs b/src/Microsoft.OpenApi/Any/OpenApiByte.cs index a0cc3a1ac..5e91b888e 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiByte.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiByte.cs @@ -29,4 +29,4 @@ public OpenApiByte(byte[] value) /// public override PrimitiveType PrimitiveType { get; } = PrimitiveType.Byte; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiDate.cs b/src/Microsoft.OpenApi/Any/OpenApiDate.cs index deec825c9..c285799b6 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiDate.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiDate.cs @@ -23,4 +23,4 @@ public OpenApiDate(DateTime value) /// public override PrimitiveType PrimitiveType { get; } = PrimitiveType.Date; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiDateTime.cs b/src/Microsoft.OpenApi/Any/OpenApiDateTime.cs index 4f885fedd..81b647288 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiDateTime.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiDateTime.cs @@ -23,4 +23,4 @@ public OpenApiDateTime(DateTimeOffset value) /// public override PrimitiveType PrimitiveType { get; } = PrimitiveType.DateTime; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiDouble.cs b/src/Microsoft.OpenApi/Any/OpenApiDouble.cs index a86279fb2..35711a191 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiDouble.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiDouble.cs @@ -21,4 +21,4 @@ public OpenApiDouble(double value) /// public override PrimitiveType PrimitiveType { get; } = PrimitiveType.Double; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiFloat.cs b/src/Microsoft.OpenApi/Any/OpenApiFloat.cs index fd0189835..3a64fb04c 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiFloat.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiFloat.cs @@ -21,4 +21,4 @@ public OpenApiFloat(float value) /// public override PrimitiveType PrimitiveType { get; } = PrimitiveType.Float; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiInteger.cs b/src/Microsoft.OpenApi/Any/OpenApiInteger.cs index aa7a9284d..a0aa88fe8 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiInteger.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiInteger.cs @@ -21,4 +21,4 @@ public OpenApiInteger(int value) /// public override PrimitiveType PrimitiveType { get; } = PrimitiveType.Integer; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiLong.cs b/src/Microsoft.OpenApi/Any/OpenApiLong.cs index cf3c9efa6..30b42fbf3 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiLong.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiLong.cs @@ -21,4 +21,4 @@ public OpenApiLong(long value) /// public override PrimitiveType PrimitiveType { get; } = PrimitiveType.Long; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiNull.cs b/src/Microsoft.OpenApi/Any/OpenApiNull.cs index de6d4ff79..229409d95 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiNull.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiNull.cs @@ -25,4 +25,4 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) writer.WriteAny(this); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiObject.cs b/src/Microsoft.OpenApi/Any/OpenApiObject.cs index 795f9dbe6..cd2f6ee64 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiObject.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiObject.cs @@ -35,4 +35,4 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiPassword.cs b/src/Microsoft.OpenApi/Any/OpenApiPassword.cs index caf771690..aaa56e72b 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiPassword.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiPassword.cs @@ -21,4 +21,4 @@ public OpenApiPassword(string value) /// public override PrimitiveType PrimitiveType { get; } = PrimitiveType.Password; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiPrimitive.cs b/src/Microsoft.OpenApi/Any/OpenApiPrimitive.cs index 1207c96df..624dbd395 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiPrimitive.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiPrimitive.cs @@ -128,4 +128,4 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Any/OpenApiString.cs b/src/Microsoft.OpenApi/Any/OpenApiString.cs index ebd31449e..e1036cfca 100644 --- a/src/Microsoft.OpenApi/Any/OpenApiString.cs +++ b/src/Microsoft.OpenApi/Any/OpenApiString.cs @@ -8,18 +8,30 @@ namespace Microsoft.OpenApi.Any /// public class OpenApiString : OpenApiPrimitive { + private bool isExplicit; + /// /// Initializes the class. /// /// - public OpenApiString(string value) + /// Used to indicate if a string is quoted. + public OpenApiString(string value, bool isExplicit = false) : base(value) { + this.isExplicit = isExplicit; } /// /// The primitive class this object represents. /// public override PrimitiveType PrimitiveType { get; } = PrimitiveType.String; + + /// + /// True if string was specified explicitly by the means of double quotes, single quotes, or literal or folded style. + /// + public bool IsExplicit() + { + return this.isExplicit; + } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Attributes/DisplayAttribute.cs b/src/Microsoft.OpenApi/Attributes/DisplayAttribute.cs index 0d30d313a..920593bbd 100644 --- a/src/Microsoft.OpenApi/Attributes/DisplayAttribute.cs +++ b/src/Microsoft.OpenApi/Attributes/DisplayAttribute.cs @@ -30,4 +30,4 @@ public DisplayAttribute(string name) /// public string Name { get; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Error.cs b/src/Microsoft.OpenApi/Error.cs index 4d9af0810..d0c41780d 100644 --- a/src/Microsoft.OpenApi/Error.cs +++ b/src/Microsoft.OpenApi/Error.cs @@ -95,4 +95,4 @@ internal static NotSupportedException NotSupported(string messageFormat, params return new NotSupportedException(Format(messageFormat, messageArgs)); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Exceptions/OpenApiException.cs b/src/Microsoft.OpenApi/Exceptions/OpenApiException.cs index 78c2ff6a8..410e0dd78 100644 --- a/src/Microsoft.OpenApi/Exceptions/OpenApiException.cs +++ b/src/Microsoft.OpenApi/Exceptions/OpenApiException.cs @@ -43,4 +43,4 @@ public OpenApiException(string message, Exception innerException) /// public string Pointer { get; set; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Exceptions/OpenApiWriterException.cs b/src/Microsoft.OpenApi/Exceptions/OpenApiWriterException.cs index fca2538ef..494608b1f 100644 --- a/src/Microsoft.OpenApi/Exceptions/OpenApiWriterException.cs +++ b/src/Microsoft.OpenApi/Exceptions/OpenApiWriterException.cs @@ -38,4 +38,4 @@ public OpenApiWriterException(string message, Exception innerException) { } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Expressions/BodyExpression.cs b/src/Microsoft.OpenApi/Expressions/BodyExpression.cs index 5fc6e25dd..cd5c86bd9 100644 --- a/src/Microsoft.OpenApi/Expressions/BodyExpression.cs +++ b/src/Microsoft.OpenApi/Expressions/BodyExpression.cs @@ -66,4 +66,4 @@ public string Fragment } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Expressions/HeaderExpression.cs b/src/Microsoft.OpenApi/Expressions/HeaderExpression.cs index 2165ca230..f6642cb08 100644 --- a/src/Microsoft.OpenApi/Expressions/HeaderExpression.cs +++ b/src/Microsoft.OpenApi/Expressions/HeaderExpression.cs @@ -48,4 +48,4 @@ public string Token } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Expressions/RuntimeExpression.cs b/src/Microsoft.OpenApi/Expressions/RuntimeExpression.cs index 5132efb64..965572dfd 100644 --- a/src/Microsoft.OpenApi/Expressions/RuntimeExpression.cs +++ b/src/Microsoft.OpenApi/Expressions/RuntimeExpression.cs @@ -34,9 +34,9 @@ public static RuntimeExpression Build(string expression) throw Error.ArgumentNullOrWhiteSpace(nameof(expression)); } - if ( !expression.StartsWith( Prefix ) ) + if (!expression.StartsWith(Prefix)) { - return new CompositeExpression( expression ); + return new CompositeExpression(expression); } // $url @@ -73,7 +73,7 @@ public static RuntimeExpression Build(string expression) return new ResponseExpression(source); } - throw new OpenApiException( string.Format( SRResource.RuntimeExpressionHasInvalidFormat, expression ) ); + throw new OpenApiException(string.Format(SRResource.RuntimeExpressionHasInvalidFormat, expression)); } /// diff --git a/src/Microsoft.OpenApi/Expressions/UrlExpression.cs b/src/Microsoft.OpenApi/Expressions/UrlExpression.cs index 16ca00f0a..4dc10bb77 100644 --- a/src/Microsoft.OpenApi/Expressions/UrlExpression.cs +++ b/src/Microsoft.OpenApi/Expressions/UrlExpression.cs @@ -25,4 +25,4 @@ public UrlExpression() { } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs index f146e0ee7..d7c778c19 100644 --- a/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/EnumExtensions.cs @@ -43,4 +43,4 @@ public static string GetDisplayName(this Enum enumValue) return attribute == null ? enumValue.ToString() : attribute.Name; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Extensions/OpenAPIWriterExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenAPIWriterExtensions.cs new file mode 100644 index 000000000..a32807ab6 --- /dev/null +++ b/src/Microsoft.OpenApi/Extensions/OpenAPIWriterExtensions.cs @@ -0,0 +1,26 @@ +using Microsoft.OpenApi.Writers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.OpenApi +{ + internal static class OpenAPIWriterExtensions + { + /// + /// Temporary extension method until we add Settings property to IOpenApiWriter in next major version + /// + /// + /// + internal static OpenApiWriterSettings GetSettings(this IOpenApiWriter openApiWriter) + { + if (openApiWriter is OpenApiWriterBase) + { + return ((OpenApiWriterBase)openApiWriter).Settings; + } + return new OpenApiWriterSettings(); + } + } +} diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs index f7e138e2e..81893a3b2 100644 --- a/src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiElementExtensions.cs @@ -28,4 +28,4 @@ public static IEnumerable Validate(this IOpenApiElement element, V return validator.Errors; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiExtensibleExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiExtensibleExtensions.cs index b41802794..aee0d44a5 100644 --- a/src/Microsoft.OpenApi/Extensions/OpenApiExtensibleExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiExtensibleExtensions.cs @@ -42,4 +42,4 @@ public static void AddExtension(this T element, string name, IOpenApiExtensio element.Extensions[name] = any ?? throw Error.ArgumentNull(nameof(any)); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs index 2f943148f..e14c98340 100644 --- a/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/OpenApiSerializableExtensions.cs @@ -50,11 +50,13 @@ public static void SerializeAsYaml(this T element, Stream stream, OpenApiSpec /// The given stream. /// The Open API specification version. /// The output format (JSON or YAML). + /// Provide configuration settings for controlling writing output public static void Serialize( this T element, Stream stream, OpenApiSpecVersion specVersion, - OpenApiFormat format) + OpenApiFormat format, + OpenApiWriterSettings settings = null) where T : IOpenApiSerializable { if (stream == null) @@ -67,10 +69,10 @@ public static void Serialize( switch (format) { case OpenApiFormat.Json: - writer = new OpenApiJsonWriter(streamWriter); + writer = new OpenApiJsonWriter(streamWriter,settings); break; case OpenApiFormat.Yaml: - writer = new OpenApiYamlWriter(streamWriter); + writer = new OpenApiYamlWriter(streamWriter, settings); break; default: throw new OpenApiException(string.Format(SRResource.OpenApiFormatNotSupported, format)); @@ -86,6 +88,7 @@ public static void Serialize( /// The Open API element. /// The output writer. /// Version of the specification the output should conform to + public static void Serialize(this T element, IOpenApiWriter writer, OpenApiSpecVersion specVersion) where T : IOpenApiSerializable { @@ -116,6 +119,7 @@ public static void Serialize(this T element, IOpenApiWriter writer, OpenApiSp writer.Flush(); } + /// /// Serializes the to the Open API document as a string in JSON format. /// @@ -174,4 +178,4 @@ public static string Serialize( } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Extensions/StringExtensions.cs b/src/Microsoft.OpenApi/Extensions/StringExtensions.cs index 1f8fa9f16..980abda86 100644 --- a/src/Microsoft.OpenApi/Extensions/StringExtensions.cs +++ b/src/Microsoft.OpenApi/Extensions/StringExtensions.cs @@ -38,4 +38,4 @@ public static T GetEnumFromDisplayName(this string displayName) return default(T); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiElement.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiElement.cs index 59279eb3c..45f7bad11 100644 --- a/src/Microsoft.OpenApi/Interfaces/IOpenApiElement.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiElement.cs @@ -9,4 +9,4 @@ namespace Microsoft.OpenApi.Interfaces public interface IOpenApiElement { } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiExtensible.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiExtensible.cs index 1760c14e3..7abd1bfdd 100644 --- a/src/Microsoft.OpenApi/Interfaces/IOpenApiExtensible.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiExtensible.cs @@ -16,4 +16,4 @@ public interface IOpenApiExtensible : IOpenApiElement /// IDictionary Extensions { get; set; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs index cf39f5004..eb47c64bc 100644 --- a/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiReferenceable.cs @@ -15,7 +15,7 @@ public interface IOpenApiReferenceable : IOpenApiSerializable /// /// Indicates if object is populated with data or is just a reference to the data /// - bool UnresolvedReference { get; set;} + bool UnresolvedReference { get; set; } /// /// Reference object. @@ -32,4 +32,4 @@ public interface IOpenApiReferenceable : IOpenApiSerializable /// void SerializeAsV2WithoutReference(IOpenApiWriter writer); } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Interfaces/IOpenApiSerializable.cs b/src/Microsoft.OpenApi/Interfaces/IOpenApiSerializable.cs index d33ec2088..582bd49cd 100644 --- a/src/Microsoft.OpenApi/Interfaces/IOpenApiSerializable.cs +++ b/src/Microsoft.OpenApi/Interfaces/IOpenApiSerializable.cs @@ -22,4 +22,4 @@ public interface IOpenApiSerializable : IOpenApiElement /// The writer. void SerializeAsV2(IOpenApiWriter writer); } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/JsonPointer.cs b/src/Microsoft.OpenApi/JsonPointer.cs index 6ef6b95ef..07e1305d6 100644 --- a/src/Microsoft.OpenApi/JsonPointer.cs +++ b/src/Microsoft.OpenApi/JsonPointer.cs @@ -66,4 +66,4 @@ public override string ToString() return "/" + string.Join("/", Tokens); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj index 3ece126b1..d7cf048eb 100644 --- a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj +++ b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj @@ -10,7 +10,7 @@ Microsoft Microsoft.OpenApi Microsoft.OpenApi - 1.1.4 + 1.2.0 .NET models with JSON and YAML writers for OpenAPI specification © Microsoft Corporation. All rights reserved. OpenAPI .NET diff --git a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs index fc6db76dc..4f685d2de 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiCallback.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiCallback.cs @@ -23,7 +23,7 @@ public class OpenApiCallback : IOpenApiSerializable, IOpenApiReferenceable, IOpe /// /// Indicates if object is populated with data or is just a reference to the data /// - public bool UnresolvedReference { get; set;} + public bool UnresolvedReference { get; set; } /// /// Reference pointer. @@ -70,12 +70,12 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null) + if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) { Reference.SerializeAsV3(writer); return; } - + SerializeAsV3WithoutReference(writer); } @@ -116,4 +116,4 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // Callback object does not exist in V2. } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs index 6adab4b13..08b8bd020 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiComponents.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiComponents.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; +using System.Linq; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -76,6 +78,28 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } + // If references have been inlined we don't need the to render the components section + // however if they have cycles, then we will need a component rendered + if (writer.GetSettings().ReferenceInline != ReferenceInlineSetting.DoNotInlineReferences) + { + var loops = writer.GetSettings().LoopDetector.Loops; + writer.WriteStartObject(); + if (loops.TryGetValue(typeof(OpenApiSchema), out List schemas)) + { + var openApiSchemas = schemas.Cast().Distinct().ToList() + .ToDictionary(k => k.Reference.Id); + + writer.WriteOptionalMap( + OpenApiConstants.Schemas, + Schemas, + (w, key, component) => { + component.SerializeAsV3WithoutReference(w); + }); + } + writer.WriteEndObject(); + return; + } + writer.WriteStartObject(); // Serialize each referenceable object as full object without reference if the reference in the object points to itself. @@ -257,4 +281,4 @@ public void SerializeAsV2(IOpenApiWriter writer) // Components object does not exist in V2. } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs index 526ed25e1..11e6d8f70 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiConstants.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiConstants.cs @@ -624,4 +624,4 @@ public static class OpenApiConstants #endregion } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiContact.cs b/src/Microsoft.OpenApi/Models/OpenApiContact.cs index ecba3d3c4..848560ead 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiContact.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiContact.cs @@ -75,4 +75,4 @@ private void WriteInternal(IOpenApiWriter writer, OpenApiSpecVersion specVersion writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs b/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs index cee77aadb..a0739dc25 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDiscriminator.cs @@ -51,4 +51,4 @@ public void SerializeAsV2(IOpenApiWriter writer) // Discriminator object does not exist in V2. } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 78d532f22..52a1b14ae 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -4,9 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Services; using Microsoft.OpenApi.Writers; namespace Microsoft.OpenApi.Models @@ -126,27 +128,53 @@ public void SerializeAsV2(IOpenApiWriter writer) // paths writer.WriteRequiredObject(OpenApiConstants.Paths, Paths, (w, p) => p.SerializeAsV2(w)); - // Serialize each referenceable object as full object without reference if the reference in the object points to itself. - // If the reference exists but points to other objects, the object is serialized to just that reference. + // If references have been inlined we don't need the to render the components section + // however if they have cycles, then we will need a component rendered + if (writer.GetSettings().ReferenceInline != ReferenceInlineSetting.DoNotInlineReferences) + { + var loops = writer.GetSettings().LoopDetector.Loops; - // definitions - writer.WriteOptionalMap( - OpenApiConstants.Definitions, - Components?.Schemas, - (w, key, component) => + if (loops.TryGetValue(typeof(OpenApiSchema), out List schemas)) { - if (component.Reference != null && - component.Reference.Type == ReferenceType.Schema && - component.Reference.Id == key) - { - component.SerializeAsV2WithoutReference(w); - } - else + var openApiSchemas = schemas.Cast().Distinct().ToList() + .ToDictionary(k => k.Reference.Id); + + foreach (var schema in openApiSchemas.Values.ToList()) { - component.SerializeAsV2(w); + FindSchemaReferences.ResolveSchemas(Components, openApiSchemas); } - }); + writer.WriteOptionalMap( + OpenApiConstants.Definitions, + openApiSchemas, + (w, key, component) => + { + component.SerializeAsV2WithoutReference(w); + }); + } + } + else + { + // Serialize each referenceable object as full object without reference if the reference in the object points to itself. + // If the reference exists but points to other objects, the object is serialized to just that reference. + // definitions + writer.WriteOptionalMap( + OpenApiConstants.Definitions, + Components?.Schemas, + (w, key, component) => + { + if (component.Reference != null && + component.Reference.Type == ReferenceType.Schema && + component.Reference.Id == key) + { + component.SerializeAsV2WithoutReference(w); + } + else + { + component.SerializeAsV2(w); + } + }); + } // parameters writer.WriteOptionalMap( OpenApiConstants.Parameters, @@ -232,17 +260,33 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList // Divide the URL in the Url property into host and basePath required in OpenAPI V2 // The Url property cannotcontain path templating to be valid for V2 serialization. - var firstServerUrl = new Uri(firstServer.Url); + var firstServerUrl = new Uri(firstServer.Url, UriKind.RelativeOrAbsolute); // host - writer.WriteProperty( - OpenApiConstants.Host, - firstServerUrl.GetComponents(UriComponents.Host | UriComponents.Port, UriFormat.SafeUnescaped)); - - // basePath - if (firstServerUrl.AbsolutePath != "/") + if (firstServerUrl.IsAbsoluteUri) + { + writer.WriteProperty( + OpenApiConstants.Host, + firstServerUrl.GetComponents(UriComponents.Host | UriComponents.Port, UriFormat.SafeUnescaped)); + + // basePath + if (firstServerUrl.AbsolutePath != "/") + { + writer.WriteProperty(OpenApiConstants.BasePath, firstServerUrl.AbsolutePath); + } + } else { - writer.WriteProperty(OpenApiConstants.BasePath, firstServerUrl.AbsolutePath); + var relativeUrl = firstServerUrl.OriginalString; + if (relativeUrl.StartsWith("//")) + { + var pathPosition = relativeUrl.IndexOf('/', 3); + writer.WriteProperty(OpenApiConstants.Host, relativeUrl.Substring(0, pathPosition)); + relativeUrl = relativeUrl.Substring(pathPosition); + } + if (!String.IsNullOrEmpty(relativeUrl) && relativeUrl != "/") + { + writer.WriteProperty(OpenApiConstants.BasePath, relativeUrl); + } } // Consider all schemes of the URLs in the server list that have the same @@ -260,7 +304,7 @@ private static void WriteHostInfoV2(IOpenApiWriter writer, IList UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) == - 0) + 0 && u.IsAbsoluteUri) .Select(u => u.Scheme) .Distinct() .ToList(); @@ -282,7 +326,7 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) if (reference.IsExternal) { // Should not attempt to resolve external references against a single document. - throw new ArgumentException(Properties.SRResource.RemoteReferenceNotSupported); + throw new ArgumentException(Properties.SRResource.RemoteReferenceNotSupported); } if (!reference.Type.HasValue) @@ -305,7 +349,8 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) return null; } - if (this.Components == null) { + if (this.Components == null) + { throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); } @@ -343,10 +388,54 @@ public IOpenApiReferenceable ResolveReference(OpenApiReference reference) default: throw new OpenApiException(Properties.SRResource.InvalidReferenceType); } - } catch(KeyNotFoundException) + } + catch (KeyNotFoundException) + { + throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId, reference.Id)); + } + } + } + + internal class FindSchemaReferences : OpenApiVisitorBase + { + private Dictionary Schemas; + + public static void ResolveSchemas(OpenApiComponents components, Dictionary schemas ) + { + var visitor = new FindSchemaReferences(); + visitor.Schemas = schemas; + var walker = new OpenApiWalker(visitor); + walker.Walk(components); + } + + public override void Visit(IOpenApiReferenceable referenceable) + { + switch (referenceable) + { + case OpenApiSchema schema: + if (!Schemas.ContainsKey(schema.Reference.Id)) + { + Schemas.Add(schema.Reference.Id, schema); + } + break; + + default: + break; + } + base.Visit(referenceable); + } + + public override void Visit(OpenApiSchema schema) + { + // This is needed to handle schemas used in Responses in components + if (schema.Reference != null) { - throw new OpenApiException(string.Format(Properties.SRResource.InvalidReferenceId,reference.Id)); + if (!Schemas.ContainsKey(schema.Reference.Id)) + { + Schemas.Add(schema.Reference.Id, schema); + } } + base.Visit(schema); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs b/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs index 74fb943b6..0d02c384e 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiEncoding.cs @@ -94,4 +94,4 @@ public void SerializeAsV2(IOpenApiWriter writer) // nothing here } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiError.cs b/src/Microsoft.OpenApi/Models/OpenApiError.cs index dbd34845c..98bf7ec82 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiError.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiError.cs @@ -41,7 +41,7 @@ public OpenApiError(string pointer, string message) /// public override string ToString() { - return Message + (!string.IsNullOrEmpty(Pointer) ? " [" + Pointer + "]" : "" ); + return Message + (!string.IsNullOrEmpty(Pointer) ? " [" + Pointer + "]" : ""); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiExample.cs b/src/Microsoft.OpenApi/Models/OpenApiExample.cs index 0ebce156b..d9bc08e30 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExample.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExample.cs @@ -64,7 +64,7 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null) + if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) { Reference.SerializeAsV3(writer); return; @@ -118,4 +118,4 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // in Response object, so it will be serialized as a part of the Response object. } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs b/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs index d3fc5117c..0a43255fb 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExtensibleDictionary.cs @@ -66,4 +66,4 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs b/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs index 9485eea55..a47fb9906 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiExternalDocs.cs @@ -66,4 +66,4 @@ private void WriteInternal(IOpenApiWriter writer, OpenApiSpecVersion specVersion writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs index c5fa288ae..eb6736183 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiHeader.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiHeader.cs @@ -18,7 +18,7 @@ public class OpenApiHeader : IOpenApiSerializable, IOpenApiReferenceable, IOpenA /// /// Indicates if object is populated with data or is just a reference to the data /// - public bool UnresolvedReference { get; set;} + public bool UnresolvedReference { get; set; } /// /// Reference pointer. @@ -96,7 +96,7 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null) + if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) { Reference.SerializeAsV3(writer); return; @@ -161,7 +161,7 @@ public void SerializeAsV2(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null) + if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) { Reference.SerializeAsV2(writer); return; @@ -210,4 +210,4 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiInfo.cs b/src/Microsoft.OpenApi/Models/OpenApiInfo.cs index 03020cf56..17364ba3b 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiInfo.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiInfo.cs @@ -121,4 +121,4 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiLicense.cs b/src/Microsoft.OpenApi/Models/OpenApiLicense.cs index dcd2223b5..0de7540c8 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLicense.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLicense.cs @@ -66,4 +66,4 @@ private void WriteInternal(IOpenApiWriter writer, OpenApiSpecVersion specVersion writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiLink.cs b/src/Microsoft.OpenApi/Models/OpenApiLink.cs index 4bd7ef4bf..fb7396db2 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiLink.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiLink.cs @@ -54,7 +54,7 @@ public class OpenApiLink : IOpenApiSerializable, IOpenApiReferenceable, IOpenApi /// /// Indicates if object is populated with data or is just a reference to the data /// - public bool UnresolvedReference { get; set;} + public bool UnresolvedReference { get; set; } /// /// Reference pointer. @@ -71,7 +71,7 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null) + if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) { Reference.SerializeAsV3(writer); return; @@ -124,4 +124,4 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // Link object does not exist in V2. } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs index 93098f836..414f46b30 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiMediaType.cs @@ -81,4 +81,4 @@ public void SerializeAsV2(IOpenApiWriter writer) // Media type does not exist in V2. } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs index ee16dc7ba..e478944aa 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlow.cs @@ -79,4 +79,4 @@ public void SerializeAsV2(IOpenApiWriter writer) // OAuthFlow object does not exist in V2. } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs index 91e17368b..fa2db10db 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOAuthFlows.cs @@ -82,4 +82,4 @@ public void SerializeAsV2(IOpenApiWriter writer) // OAuthFlows object does not exist in V2. } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs index d90637945..50c6fa584 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiOperation.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiOperation.cs @@ -314,4 +314,4 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs index a34116723..97edce131 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiParameter.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiParameter.cs @@ -17,7 +17,7 @@ public class OpenApiParameter : IOpenApiSerializable, IOpenApiReferenceable, IOp /// /// Indicates if object is populated with data or is just a reference to the data /// - public bool UnresolvedReference { get; set;} + public bool UnresolvedReference { get; set; } /// /// Reference object. @@ -101,7 +101,7 @@ public class OpenApiParameter : IOpenApiSerializable, IOpenApiReferenceable, IOp /// Furthermore, if referencing a schema which contains an example, /// the examples value SHALL override the example provided by the schema. /// - public IDictionary Examples { get; set; } = new Dictionary(); + public IDictionary Examples { get; set; } = new Dictionary(); /// /// Example of the media type. The example SHOULD match the specified schema and encoding properties @@ -139,7 +139,7 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null) + if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) { Reference.SerializeAsV3(writer); return; @@ -210,7 +210,7 @@ public void SerializeAsV2(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null) + if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) { Reference.SerializeAsV2(writer); return; @@ -252,6 +252,8 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // deprecated writer.WriteProperty(OpenApiConstants.Deprecated, Deprecated, false); + var extensionsClone = new Dictionary(Extensions); + // schema if (this is OpenApiBodyParameter) { @@ -259,7 +261,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) } // In V2 parameter's type can't be a reference to a custom object schema or can't be of type object // So in that case map the type as string. - else + else if (Schema?.UnresolvedReference == true || Schema?.Type == "object") { writer.WriteProperty(OpenApiConstants.Type, "string"); @@ -283,12 +285,25 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // uniqueItems // enum // multipleOf - Schema?.WriteAsItemsProperties(writer); + if (Schema != null) + { + Schema.WriteAsItemsProperties(writer); + + if (Schema.Extensions != null) + { + foreach (var key in Schema.Extensions.Keys) + { + // The extension will already have been serialized as part of the call to WriteAsItemsProperties above, + // so remove it from the cloned collection so we don't write it again. + extensionsClone.Remove(key); + } + } + } // allowEmptyValue writer.WriteProperty(OpenApiConstants.AllowEmptyValue, AllowEmptyValue, false); - if (this.In == ParameterLocation.Query ) + if (this.In == ParameterLocation.Query) { if (this.Style == ParameterStyle.Form && this.Explode == true) { @@ -307,7 +322,7 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // extensions - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); + writer.WriteExtensions(extensionsClone, OpenApiSpecVersion.OpenApi2_0); writer.WriteEndObject(); } @@ -326,4 +341,4 @@ internal class OpenApiBodyParameter : OpenApiParameter internal class OpenApiFormDataParameter : OpenApiParameter { } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs index aecabdefb..3143e94cb 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiPathItem.cs @@ -136,4 +136,4 @@ public void SerializeAsV2(IOpenApiWriter writer) writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiPaths.cs b/src/Microsoft.OpenApi/Models/OpenApiPaths.cs index c998a101a..72d0576d3 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiPaths.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiPaths.cs @@ -9,4 +9,4 @@ namespace Microsoft.OpenApi.Models public class OpenApiPaths : OpenApiExtensibleDictionary { } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiReference.cs b/src/Microsoft.OpenApi/Models/OpenApiReference.cs index 6e925efd5..2c8f738ca 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiReference.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiReference.cs @@ -210,4 +210,4 @@ private string GetReferenceTypeNameAsV2(ReferenceType type) } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs index 9b2842e8b..d6308efcf 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiRequestBody.cs @@ -16,7 +16,7 @@ public class OpenApiRequestBody : IOpenApiSerializable, IOpenApiReferenceable, I /// /// Indicates if object is populated with data or is just a reference to the data /// - public bool UnresolvedReference { get; set;} + public bool UnresolvedReference { get; set; } /// /// Reference object. @@ -102,4 +102,4 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) // RequestBody object does not exist in V2. } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index 4a4c5491e..bc2e4e525 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Writers; @@ -45,7 +44,7 @@ public class OpenApiResponse : IOpenApiSerializable, IOpenApiReferenceable, IOpe /// /// Indicates if object is populated with data or is just a reference to the data /// - public bool UnresolvedReference { get; set;} + public bool UnresolvedReference { get; set; } /// /// Reference pointer. @@ -62,7 +61,7 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null) + if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) { Reference.SerializeAsV3(writer); return; @@ -79,7 +78,7 @@ public void SerializeAsV3WithoutReference(IOpenApiWriter writer) writer.WriteStartObject(); // description - writer.WriteProperty(OpenApiConstants.Description, Description); + writer.WriteRequiredProperty(OpenApiConstants.Description, Description); // headers writer.WriteOptionalMap(OpenApiConstants.Headers, Headers, (w, h) => h.SerializeAsV3(w)); @@ -106,7 +105,7 @@ public void SerializeAsV2(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } - if (Reference != null) + if (Reference != null && writer.GetSettings().ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) { Reference.SerializeAsV2(writer); return; @@ -123,7 +122,10 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteStartObject(); // description - writer.WriteProperty(OpenApiConstants.Description, Description); + writer.WriteRequiredProperty(OpenApiConstants.Description, Description); + + var extensionsClone = new Dictionary(Extensions); + if (Content != null) { var mediatype = Content.FirstOrDefault(); @@ -152,6 +154,15 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteEndObject(); } + + writer.WriteExtensions(mediatype.Value.Extensions, OpenApiSpecVersion.OpenApi2_0); + + foreach (var key in mediatype.Value.Extensions.Keys) + { + // The extension will already have been serialized as part of the call above, + // so remove it from the cloned collection so we don't write it again. + extensionsClone.Remove(key); + } } } @@ -159,9 +170,9 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteOptionalMap(OpenApiConstants.Headers, Headers, (w, h) => h.SerializeAsV2(w)); // extension - writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); + writer.WriteExtensions(extensionsClone, OpenApiSpecVersion.OpenApi2_0); writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponses.cs b/src/Microsoft.OpenApi/Models/OpenApiResponses.cs index 0244d99cf..818bf4ced 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponses.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponses.cs @@ -9,4 +9,4 @@ namespace Microsoft.OpenApi.Models public class OpenApiResponses : OpenApiExtensibleDictionary { } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 008f4ae21..10760994f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -251,13 +251,31 @@ public void SerializeAsV3(IOpenApiWriter writer) throw Error.ArgumentNull(nameof(writer)); } + var settings = writer.GetSettings(); + if (Reference != null) { - Reference.SerializeAsV3(writer); - return; + if (settings.ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + { + Reference.SerializeAsV3(writer); + return; + } + + // If Loop is detected then just Serialize as a reference. + if (!settings.LoopDetector.PushLoop(this)) + { + settings.LoopDetector.SaveLoop(this); + Reference.SerializeAsV3(writer); + return; + } } SerializeAsV3WithoutReference(writer); + + if (Reference != null) + { + settings.LoopDetector.PopLoop(); + } } /// @@ -426,10 +444,23 @@ internal void SerializeAsV2( if (Reference != null) { - Reference.SerializeAsV2(writer); - return; + var settings = writer.GetSettings(); + if (settings.ReferenceInline != ReferenceInlineSetting.InlineLocalReferences) + { + Reference.SerializeAsV2(writer); + return; + } + + // If Loop is detected then just Serialize as a reference. + if (!settings.LoopDetector.PushLoop(this)) + { + settings.LoopDetector.SaveLoop(this); + Reference.SerializeAsV2(writer); + return; + } } + if (parentRequiredProperties == null) { parentRequiredProperties = new HashSet(); @@ -629,4 +660,4 @@ internal void WriteAsSchemaProperties( writer.WriteExtensions(Extensions, OpenApiSpecVersion.OpenApi2_0); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs index a94797b6f..d2564daf2 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs @@ -146,4 +146,4 @@ public int GetHashCode(OpenApiSecurityScheme obj) } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs index f77893592..7694c5fd4 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs @@ -66,7 +66,7 @@ public class OpenApiSecurityScheme : IOpenApiSerializable, IOpenApiReferenceable /// /// Indicates if object is populated with data or is just a reference to the data /// - public bool UnresolvedReference { get; set;} + public bool UnresolvedReference { get; set; } /// /// Reference object. @@ -259,4 +259,4 @@ private static void WriteOAuthFlowForV2(IOpenApiWriter writer, string flowValue, writer.WriteOptionalMap(OpenApiConstants.Scopes, flow.Scopes, (w, s) => w.WriteValue(s)); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiServer.cs b/src/Microsoft.OpenApi/Models/OpenApiServer.cs index 72cf492d5..ea988ec13 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiServer.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiServer.cs @@ -71,4 +71,4 @@ public void SerializeAsV2(IOpenApiWriter writer) // Server object does not exist in V2. } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs b/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs index 3a8c462c5..8ae39a04c 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiServerVariable.cs @@ -69,4 +69,4 @@ public void SerializeAsV2(IOpenApiWriter writer) // ServerVariable does not exist in V2. } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiTag.cs b/src/Microsoft.OpenApi/Models/OpenApiTag.cs index 10e4bf0d1..4d743a13a 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiTag.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiTag.cs @@ -36,7 +36,7 @@ public class OpenApiTag : IOpenApiSerializable, IOpenApiReferenceable, IOpenApiE /// /// Indicates if object is populated with data or is just a reference to the data /// - public bool UnresolvedReference { get; set;} + public bool UnresolvedReference { get; set; } /// /// Reference. @@ -125,4 +125,4 @@ public void SerializeAsV2WithoutReference(IOpenApiWriter writer) writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OpenApiXml.cs b/src/Microsoft.OpenApi/Models/OpenApiXml.cs index 24d084eb9..59218970f 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiXml.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiXml.cs @@ -92,4 +92,4 @@ private void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/OperationType.cs b/src/Microsoft.OpenApi/Models/OperationType.cs index 8c0d9038e..a85933d9b 100644 --- a/src/Microsoft.OpenApi/Models/OperationType.cs +++ b/src/Microsoft.OpenApi/Models/OperationType.cs @@ -50,4 +50,4 @@ public enum OperationType /// [Display("trace")] Trace } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/ParameterLocation.cs b/src/Microsoft.OpenApi/Models/ParameterLocation.cs index 4b15a335c..a729f314e 100644 --- a/src/Microsoft.OpenApi/Models/ParameterLocation.cs +++ b/src/Microsoft.OpenApi/Models/ParameterLocation.cs @@ -31,4 +31,4 @@ public enum ParameterLocation /// [Display("cookie")] Cookie } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/ParameterStyle.cs b/src/Microsoft.OpenApi/Models/ParameterStyle.cs index 5837d3704..a1df27962 100644 --- a/src/Microsoft.OpenApi/Models/ParameterStyle.cs +++ b/src/Microsoft.OpenApi/Models/ParameterStyle.cs @@ -45,4 +45,4 @@ public enum ParameterStyle /// [Display("deepObject")] DeepObject } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/ReferenceType.cs b/src/Microsoft.OpenApi/Models/ReferenceType.cs index 18205dcb4..6ac0c9ed2 100644 --- a/src/Microsoft.OpenApi/Models/ReferenceType.cs +++ b/src/Microsoft.OpenApi/Models/ReferenceType.cs @@ -60,4 +60,4 @@ public enum ReferenceType /// [Display("tags")] Tag } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/RuntimeExpressionAnyWrapper.cs b/src/Microsoft.OpenApi/Models/RuntimeExpressionAnyWrapper.cs index 1bff9c146..12a525b4f 100644 --- a/src/Microsoft.OpenApi/Models/RuntimeExpressionAnyWrapper.cs +++ b/src/Microsoft.OpenApi/Models/RuntimeExpressionAnyWrapper.cs @@ -68,4 +68,4 @@ public void WriteValue(IOpenApiWriter writer) } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Models/SecuritySchemeType.cs b/src/Microsoft.OpenApi/Models/SecuritySchemeType.cs index a52b6a70a..d75524bd6 100644 --- a/src/Microsoft.OpenApi/Models/SecuritySchemeType.cs +++ b/src/Microsoft.OpenApi/Models/SecuritySchemeType.cs @@ -30,4 +30,4 @@ public enum SecuritySchemeType /// [Display("openIdConnect")] OpenIdConnect } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/OpenApiFormat.cs b/src/Microsoft.OpenApi/OpenApiFormat.cs index 140d71bb1..8f4c55b68 100644 --- a/src/Microsoft.OpenApi/OpenApiFormat.cs +++ b/src/Microsoft.OpenApi/OpenApiFormat.cs @@ -18,4 +18,4 @@ public enum OpenApiFormat /// Yaml } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/OpenApiSpecVersion.cs b/src/Microsoft.OpenApi/OpenApiSpecVersion.cs index 687f6d774..20e49af80 100644 --- a/src/Microsoft.OpenApi/OpenApiSpecVersion.cs +++ b/src/Microsoft.OpenApi/OpenApiSpecVersion.cs @@ -18,4 +18,4 @@ public enum OpenApiSpecVersion /// OpenApi3_0 } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Properties/AssemblyInfo.cs b/src/Microsoft.OpenApi/Properties/AssemblyInfo.cs index 24ddadb3e..159c979dd 100644 --- a/src/Microsoft.OpenApi/Properties/AssemblyInfo.cs +++ b/src/Microsoft.OpenApi/Properties/AssemblyInfo.cs @@ -8,4 +8,4 @@ "Microsoft.OpenApi.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] [assembly: InternalsVisibleTo( - "Microsoft.OpenApi.Readers.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] \ No newline at end of file + "Microsoft.OpenApi.Readers.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100957cb48387b2a5f54f5ce39255f18f26d32a39990db27cf48737afc6bc62759ba996b8a2bfb675d4e39f3d06ecb55a178b1b4031dcb2a767e29977d88cce864a0d16bfc1b3bebb0edf9fe285f10fffc0a85f93d664fa05af07faa3aad2e545182dbf787e3fd32b56aca95df1a3c4e75dec164a3f1a4c653d971b01ffc39eb3c4")] diff --git a/src/Microsoft.OpenApi/Services/LoopDetector.cs b/src/Microsoft.OpenApi/Services/LoopDetector.cs new file mode 100644 index 000000000..249cab51d --- /dev/null +++ b/src/Microsoft.OpenApi/Services/LoopDetector.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.OpenApi.Services +{ + internal class LoopDetector + { + private readonly Dictionary> _loopStacks = new Dictionary>(); + + /// + /// Maintain history of traversals to avoid stack overflows from cycles + /// + /// Identifier used for current context. + /// If method returns false a loop was detected and the key is not added. + public bool PushLoop(T key) + { + Stack stack; + if (!_loopStacks.TryGetValue(typeof(T), out stack)) + { + stack = new Stack(); + _loopStacks.Add(typeof(T), stack); + } + + if (!stack.Contains(key)) + { + stack.Push(key); + return true; + } + else + { + return false; // Loop detected + } + } + + /// + /// Exit from the context in cycle detection + /// + public void PopLoop() + { + if (_loopStacks[typeof(T)].Count > 0) + { + _loopStacks[typeof(T)].Pop(); + } + } + + public void SaveLoop(T loop) + { + if (!Loops.ContainsKey(typeof(T))) + { + Loops[typeof(T)] = new List(); + } + Loops[typeof(T)].Add(loop); + } + + /// + /// List of Loops detected + /// + public Dictionary> Loops { get; } = new Dictionary>(); + + /// + /// Reset loop tracking stack + /// + internal void ClearLoop() + { + _loopStacks[typeof(T)].Clear(); + } + } +} diff --git a/src/Microsoft.OpenApi/Services/OpenApiComparer.cs b/src/Microsoft.OpenApi/Services/OpenApiComparer.cs deleted file mode 100644 index a8faa625a..000000000 --- a/src/Microsoft.OpenApi/Services/OpenApiComparer.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using System.Collections.Generic; -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Services -{ - /// - /// Class containing logic to get differences between two s. - /// - public static class OpenApiComparer - { - /// - /// Compares two s and returns a list of differences. - /// - public static List Compare(OpenApiDocument source, OpenApiDocument target) - { - var diffs = new List(); - return diffs; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiDifference.cs b/src/Microsoft.OpenApi/Services/OpenApiDifference.cs deleted file mode 100644 index aea6d19f2..000000000 --- a/src/Microsoft.OpenApi/Services/OpenApiDifference.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.OpenApi.Models; - -namespace Microsoft.OpenApi.Services -{ - /// - /// Difference point between two - /// - public class OpenApiDifference - { - } -} \ No newline at end of file diff --git a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs index cb0430956..bc65fdfc2 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiVisitorBase.cs @@ -303,7 +303,7 @@ public virtual void Visit(IList openApiTags) public virtual void Visit(IList openApiSecurityRequirements) { } - + /// /// Visits /// @@ -348,4 +348,4 @@ public virtual void Visit(IOpenApiReferenceable referenceable) { } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs index 065faddf3..dd3c09564 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiWalker.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiWalker.cs @@ -43,7 +43,7 @@ public void Walk(OpenApiDocument doc) _visitor.Visit(doc); - Walk(OpenApiConstants.Info,() => Walk(doc.Info)); + Walk(OpenApiConstants.Info, () => Walk(doc.Info)); Walk(OpenApiConstants.Servers, () => Walk(doc.Servers)); Walk(OpenApiConstants.Paths, () => Walk(doc.Paths)); Walk(OpenApiConstants.Components, () => Walk(doc.Components)); @@ -238,7 +238,7 @@ internal void Walk(IList servers) { for (int i = 0; i < servers.Count; i++) { - Walk(i.ToString(),() => Walk(servers[i])); + Walk(i.ToString(), () => Walk(servers[i])); } } } @@ -254,7 +254,8 @@ internal void Walk(OpenApiInfo info) } _visitor.Visit(info); - if (info != null) { + if (info != null) + { Walk(OpenApiConstants.Contact, () => Walk(info.Contact)); Walk(OpenApiConstants.License, () => Walk(info.License)); } @@ -380,7 +381,7 @@ internal void Walk(OpenApiServer server) /// /// Visits dictionary of /// - internal void Walk(IDictionary serverVariables) + internal void Walk(IDictionary serverVariables) { if (serverVariables == null) { @@ -684,13 +685,13 @@ internal void Walk(IDictionary content) /// internal void Walk(OpenApiMediaType mediaType) { - if (mediaType == null) + if (mediaType == null) { return; } _visitor.Visit(mediaType); - + Walk(OpenApiConstants.Example, () => Walk(mediaType.Examples)); Walk(OpenApiConstants.Schema, () => Walk(mediaType.Schema)); Walk(OpenApiConstants.Encoding, () => Walk(mediaType.Encoding)); @@ -747,14 +748,16 @@ internal void Walk(OpenApiSchema schema, bool isComponent = false) if (_schemaLoop.Contains(schema)) { return; // Loop detected, this schema has already been walked. - } else + } + else { _schemaLoop.Push(schema); } _visitor.Visit(schema); - if (schema.Items != null) { + if (schema.Items != null) + { Walk("items", () => Walk(schema.Items)); } @@ -768,7 +771,13 @@ internal void Walk(OpenApiSchema schema, bool isComponent = false) Walk("anyOf", () => Walk(schema.AnyOf)); } - if (schema.Properties != null) { + if (schema.OneOf != null) + { + Walk("oneOf", () => Walk(schema.OneOf)); + } + + if (schema.Properties != null) + { Walk("properties", () => { foreach (var item in schema.Properties) @@ -788,7 +797,7 @@ internal void Walk(OpenApiSchema schema, bool isComponent = false) /// /// Visits dictionary of /// - internal void Walk(IDictionary examples) + internal void Walk(IDictionary examples) { if (examples == null) { @@ -876,7 +885,7 @@ internal void Walk(IList schemas) } } } - + /// /// Visits and child objects /// @@ -907,7 +916,7 @@ internal void Walk(OpenApiOAuthFlow oAuthFlow) /// /// Visits dictionary of and child objects /// - internal void Walk(IDictionary links) + internal void Walk(IDictionary links) { if (links == null) { @@ -1127,4 +1136,4 @@ public class CurrentKeys /// public string ServerVariable { get; internal set; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Validations/IValidationContext.cs b/src/Microsoft.OpenApi/Validations/IValidationContext.cs index db2f32019..58f324bab 100644 --- a/src/Microsoft.OpenApi/Validations/IValidationContext.cs +++ b/src/Microsoft.OpenApi/Validations/IValidationContext.cs @@ -6,7 +6,7 @@ namespace Microsoft.OpenApi.Validations /// /// Constrained interface used to provide context to rule implementation /// - public interface IValidationContext + public interface IValidationContext { /// /// Register an error with the validation context. diff --git a/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs index 87d747507..69bcda93d 100644 --- a/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs +++ b/src/Microsoft.OpenApi/Validations/OpenApiValidator.cs @@ -13,7 +13,7 @@ namespace Microsoft.OpenApi.Validations /// /// Class containing dispatchers to execute validation rules on for Open API document. /// - public class OpenApiValidator : OpenApiVisitorBase, IValidationContext + public class OpenApiValidator : OpenApiVisitorBase, IValidationContext { private readonly ValidationRuleSet _ruleSet; private readonly IList _errors = new List(); @@ -22,11 +22,11 @@ public class OpenApiValidator : OpenApiVisitorBase, IValidationContext /// Create a vistor that will validate an OpenAPIDocument /// /// - public OpenApiValidator(ValidationRuleSet ruleSet) + public OpenApiValidator(ValidationRuleSet ruleSet) { _ruleSet = ruleSet; } - + /// /// Gets the validation errors. /// @@ -195,7 +195,7 @@ private void Validate(object item, Type type) var potentialReference = item as IOpenApiReferenceable; if (potentialReference != null && potentialReference.UnresolvedReference) { - type = typeof(IOpenApiReferenceable); + type = typeof(IOpenApiReferenceable); } var rules = _ruleSet.FindRules(type); @@ -205,4 +205,4 @@ private void Validate(object item, Type type) } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs index 624059705..60267a26d 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs @@ -57,7 +57,7 @@ private static void ValidateKeys(IValidationContext context, IEnumerable { if (!KeyRegex.IsMatch(key)) { - context.CreateError(nameof(KeyMustBeRegularExpression), + context.CreateError(nameof(KeyMustBeRegularExpression), string.Format(SRResource.Validation_ComponentsKeyMustMatchRegularExpr, key, component, KeyRegex.ToString())); } } diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs index 6380a7899..f518453f7 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiContactRules.cs @@ -26,7 +26,7 @@ public static class OpenApiContactRules { if (!item.Email.IsEmailAddress()) { - context.CreateError(nameof(EmailMustBeEmailFormat), + context.CreateError(nameof(EmailMustBeEmailFormat), String.Format(SRResource.Validation_StringMustBeEmailAddress, item.Email)); } } diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs index 789ed5c59..e5193b4c2 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiDocumentRules.cs @@ -33,7 +33,7 @@ public static class OpenApiDocumentRules context.Enter("paths"); if (item.Paths == null) { - context.CreateError(nameof(OpenApiDocumentFieldIsMissing), + context.CreateError(nameof(OpenApiDocumentFieldIsMissing), String.Format(SRResource.Validation_FieldIsRequired, "paths", "document")); } context.Exit(); diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs index f96901abe..e02f019e2 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiInfoRules.cs @@ -34,7 +34,7 @@ public static class OpenApiInfoRules context.Enter("version"); if (item.Version == null) { - context.CreateError(nameof(InfoRequiredFields), + context.CreateError(nameof(InfoRequiredFields), String.Format(SRResource.Validation_FieldIsRequired, "version", "info")); } context.Exit(); diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiOAuthFlowRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiOAuthFlowRules.cs index 37c0dfb1b..7d900e1b4 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiOAuthFlowRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiOAuthFlowRules.cs @@ -33,7 +33,7 @@ public static class OpenApiOAuthFlowRules context.Enter("tokenUrl"); if (flow.TokenUrl == null) { - context.CreateError(nameof(OAuthFlowRequiredFields), + context.CreateError(nameof(OAuthFlowRequiredFields), String.Format(SRResource.Validation_FieldIsRequired, "tokenUrl", "OAuth Flow")); } context.Exit(); @@ -42,7 +42,7 @@ public static class OpenApiOAuthFlowRules context.Enter("scopes"); if (flow.Scopes == null) { - context.CreateError(nameof(OAuthFlowRequiredFields), + context.CreateError(nameof(OAuthFlowRequiredFields), String.Format(SRResource.Validation_FieldIsRequired, "scopes", "OAuth Flow")); } context.Exit(); diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs index 80030b2f6..d1ad41781 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs @@ -33,7 +33,7 @@ public static class OpenApiParameterRules context.Enter("in"); if (item.In == null) { - context.CreateError(nameof(ParameterRequiredFields), + context.CreateError(nameof(ParameterRequiredFields), String.Format(SRResource.Validation_FieldIsRequired, "in", "parameter")); } context.Exit(); @@ -48,7 +48,7 @@ public static class OpenApiParameterRules { // required context.Enter("required"); - if ( item.In == ParameterLocation.Path && !item.Required ) + if (item.In == ParameterLocation.Path && !item.Required) { context.CreateError( nameof(RequiredMustBeTrueWhenInIsPath), diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs index 40e5eba6f..7ac374b62 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs @@ -24,7 +24,7 @@ public static class OpenApiPathsRules { context.Enter(pathName); - if (pathName == null || !pathName.StartsWith("/")) + if (pathName == null || !pathName.StartsWith("/")) { context.CreateError(nameof(PathNameMustBeginWithSlash), string.Format(SRResource.Validation_PathItemMustBeginWithSlash, pathName)); @@ -36,4 +36,4 @@ public static class OpenApiPathsRules // add more rules } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponsesRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponsesRules.cs index c3bcd2fae..d05a1229c 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponsesRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiResponsesRules.cs @@ -40,7 +40,7 @@ public static class OpenApiResponsesRules if (key != "default" && !Regex.IsMatch(key, "^[1-5](?>[0-9]{2}|XX)$")) { - context.CreateError(nameof(ResponsesMustBeIdentifiedByDefaultOrStatusCode), + context.CreateError(nameof(ResponsesMustBeIdentifiedByDefaultOrStatusCode), "Responses key must be 'default', an HTTP status code, " + "or one of the following strings representing a range of HTTP status codes: " + "'1XX', '2XX', '3XX', '4XX', '5XX'"); diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs index 3d009b931..8c45c8ff9 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs @@ -67,7 +67,7 @@ public static class OpenApiSchemaRules // discriminator context.Enter("discriminator"); - if(schema.Reference != null && schema.Discriminator != null) + if (schema.Reference != null && schema.Discriminator != null) { if (!schema.Required.Contains(schema.Discriminator?.PropertyName)) { diff --git a/src/Microsoft.OpenApi/Validations/ValidationRule.cs b/src/Microsoft.OpenApi/Validations/ValidationRule.cs index 297691e8c..fdbf5c330 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRule.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRule.cs @@ -29,7 +29,7 @@ public abstract class ValidationRule /// Class containing validation rule logic for . /// /// - public class ValidationRule : ValidationRule where T: IOpenApiElement + public class ValidationRule : ValidationRule where T : IOpenApiElement { private readonly Action _validate; diff --git a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs index c6bc5854d..eca7bc8de 100644 --- a/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs +++ b/src/Microsoft.OpenApi/Validations/ValidationRuleSet.cs @@ -167,7 +167,7 @@ private static ValidationRuleSet BuildDefaultRuleSet() Type validationRuleType = typeof(ValidationRule); IEnumerable rules = typeof(ValidationRuleSet).Assembly.GetTypes() - .Where(t => t.IsClass + .Where(t => t.IsClass && t != typeof(object) && t.GetCustomAttributes(typeof(OpenApiRuleAttribute), false).Any()) .SelectMany(t2 => t2.GetProperties(BindingFlags.Static | BindingFlags.Public) @@ -175,12 +175,12 @@ private static ValidationRuleSet BuildDefaultRuleSet() foreach (var property in rules) { - var propertyValue = property.GetValue(null); // static property - ValidationRule rule = propertyValue as ValidationRule; - if (rule != null) - { - ruleSet.Add(rule); - } + var propertyValue = property.GetValue(null); // static property + ValidationRule rule = propertyValue as ValidationRule; + if (rule != null) + { + ruleSet.Add(rule); + } } return ruleSet; diff --git a/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs b/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs index 5fc3848d7..8fcbf10ed 100644 --- a/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs +++ b/src/Microsoft.OpenApi/Writers/IOpenApiWriter.cs @@ -73,4 +73,4 @@ public interface IOpenApiWriter /// void Flush(); } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs b/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs index 19e5b16ab..e4c3baa3c 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiJsonWriter.cs @@ -14,7 +14,8 @@ public class OpenApiJsonWriter : OpenApiWriterBase /// Initializes a new instance of the class. /// /// The text writer. - public OpenApiJsonWriter(TextWriter textWriter) : base(textWriter) + /// Settings for controlling how the OpenAPI document will be written out. + public OpenApiJsonWriter(TextWriter textWriter, OpenApiWriterSettings settings = null) : base(textWriter, settings) { } @@ -204,4 +205,4 @@ public override void WriteRaw(string value) Writer.Write(value); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs index a14628d42..361da3b2a 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterAnyExtensions.cs @@ -138,4 +138,4 @@ private static void WritePrimitive(this IOpenApiWriter writer, IOpenApiPrimitive primitive.Write(writer, OpenApiSpecVersion.OpenApi3_0); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs index 172e3fa2a..bcbec6b47 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using Microsoft.OpenApi.Exceptions; +using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Properties; namespace Microsoft.OpenApi.Writers @@ -14,6 +15,12 @@ namespace Microsoft.OpenApi.Writers /// public abstract class OpenApiWriterBase : IOpenApiWriter { + + /// + /// Settings for controlling how the OpenAPI document will be written out. + /// + public OpenApiWriterSettings Settings { get; set; } + /// /// The indentation string to prepand to each line for each indentation level. /// @@ -41,6 +48,20 @@ public OpenApiWriterBase(TextWriter textWriter) Scopes = new Stack(); } + /// + /// Initializes a new instance of the class. + /// + /// + /// + public OpenApiWriterBase(TextWriter textWriter, OpenApiWriterSettings settings = null) : this(textWriter) + { + if (settings == null) + { + settings = new OpenApiWriterSettings(); + } + Settings = settings; + } + /// /// Base Indentation Level. /// This denotes how many indentations are needed for the property in the base object. @@ -105,7 +126,7 @@ public void Flush() /// /// The string value. public abstract void WriteValue(string value); - + /// /// Write float value. /// @@ -226,7 +247,7 @@ public virtual void WriteValue(object value) { WriteValue((decimal)value); } - else if ( type == typeof(DateTime) || type == typeof(DateTime?) ) + else if (type == typeof(DateTime) || type == typeof(DateTime?)) { WriteValue((DateTime)value); } @@ -390,4 +411,4 @@ protected void VerifyCanWritePropertyName(string name) } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs index 8da814bbb..537273cac 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs @@ -32,6 +32,26 @@ public static void WriteProperty(this IOpenApiWriter writer, string name, string writer.WriteValue(value); } + /// + /// Write required string property. + /// + /// The writer. + /// The property name. + /// The property value. + public static void WriteRequiredProperty(this IOpenApiWriter writer, string name, string value) + { + CheckArguments(writer, name); + writer.WritePropertyName(name); + if (value == null) + { + writer.WriteNull(); + } + else + { + writer.WriteValue(value); + } + } + /// /// Write a boolean property. /// @@ -414,4 +434,4 @@ private static void CheckArguments(IOpenApiWriter writer, string name) } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs new file mode 100644 index 000000000..45eedc831 --- /dev/null +++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterSettings.cs @@ -0,0 +1,36 @@ + +using Microsoft.OpenApi.Services; + +namespace Microsoft.OpenApi.Writers +{ + /// + /// Indicates if and when the reader should convert references into complete object renderings + /// + public enum ReferenceInlineSetting + { + /// + /// Create placeholder objects with an OpenApiReference instance and UnresolvedReference set to true. + /// + DoNotInlineReferences, + /// + /// Convert local references to references of valid domain objects. + /// + InlineLocalReferences, + /// + /// Convert all references to references of valid domain objects. + /// + InlineAllReferences + } + + /// + /// Configuration settings to control how OpenAPI documents are written + /// + public class OpenApiWriterSettings + { + internal LoopDetector LoopDetector { get; } = new LoopDetector(); + /// + /// Indicates how references in the source document should be handled. + /// + public ReferenceInlineSetting ReferenceInline { get; set; } = ReferenceInlineSetting.DoNotInlineReferences; + } +} diff --git a/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs b/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs index d213e6154..ee7543b93 100644 --- a/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs +++ b/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs @@ -14,11 +14,12 @@ public class OpenApiYamlWriter : OpenApiWriterBase /// Initializes a new instance of the class. /// /// The text writer. - public OpenApiYamlWriter(TextWriter textWriter) : base(textWriter) + /// + public OpenApiYamlWriter(TextWriter textWriter, OpenApiWriterSettings settings = null) : base(textWriter, settings) { + } - /// /// Base Indentation Level. /// This denotes how many indentations are needed for the property in the base object. @@ -205,4 +206,4 @@ public override void WriteRaw(string value) Writer.Write(value); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Writers/Scope.cs b/src/Microsoft.OpenApi/Writers/Scope.cs index 2e912e397..37093cffc 100644 --- a/src/Microsoft.OpenApi/Writers/Scope.cs +++ b/src/Microsoft.OpenApi/Writers/Scope.cs @@ -59,4 +59,4 @@ public ScopeType Type /// public bool IsInArray { get; set; } = false; } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Writers/SpecialCharacterStringExtensions.cs b/src/Microsoft.OpenApi/Writers/SpecialCharacterStringExtensions.cs index 9be16bfdf..5c6831cb1 100644 --- a/src/Microsoft.OpenApi/Writers/SpecialCharacterStringExtensions.cs +++ b/src/Microsoft.OpenApi/Writers/SpecialCharacterStringExtensions.cs @@ -226,4 +226,4 @@ internal static string GetJsonCompatibleString(this string value) return $"\"{value}\""; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.OpenApi/Writers/WriterConstants.cs b/src/Microsoft.OpenApi/Writers/WriterConstants.cs index 398ffc2ee..bfc943797 100644 --- a/src/Microsoft.OpenApi/Writers/WriterConstants.cs +++ b/src/Microsoft.OpenApi/Writers/WriterConstants.cs @@ -115,4 +115,4 @@ internal static class WriterConstants /// To indicate empty array in YAML. internal const string EmptyArray = "[ ]"; } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixture.cs b/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixture.cs index f9ee5434b..64e25fcfb 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixture.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixture.cs @@ -25,4 +25,4 @@ public DefaultSettingsFixture() .WithStrictOrdering()); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixtureCollection.cs b/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixtureCollection.cs index 97f8c17fc..1b9bd1107 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixtureCollection.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixtureCollection.cs @@ -16,4 +16,4 @@ namespace Microsoft.OpenApi.Readers.Tests public class DefaultSettingsFixtureCollection : ICollectionFixture { } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index e24afc871..7866a1d63 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -12,6 +12,9 @@ true ..\..\src\Microsoft.OpenApi.snk + + + Never @@ -25,6 +28,9 @@ Never + + PreserveNewest + Never @@ -143,6 +149,7 @@ Never + Never @@ -230,16 +237,18 @@ - - + + - + - + - + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs index 2004be7b1..1bcd7b9d9 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs @@ -29,8 +29,8 @@ public void DetectedSpecificationVersionShouldBeV3_0() new OpenApiStreamReader().Read(stream, out var diagnostic); diagnostic.Should().NotBeNull(); - diagnostic.SpecificationVersion.Should().Be( OpenApiSpecVersion.OpenApi3_0); + diagnostic.SpecificationVersion.Should().Be(OpenApiSpecVersion.OpenApi3_0); } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/UnsupportedSpecVersionTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/UnsupportedSpecVersionTests.cs index f20529115..cd60aa630 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/UnsupportedSpecVersionTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/UnsupportedSpecVersionTests.cs @@ -22,8 +22,8 @@ public void ThrowOpenApiUnsupportedSpecVersionException() catch (OpenApiUnsupportedSpecVersionException exception) { exception.SpecificationVersion.Should().Be("1.0.0"); - } + } } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs index a6a8e124c..04a400b40 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ParseNodeTests.cs @@ -25,7 +25,7 @@ public void BrokenSimpleList() var reader = new OpenApiStringReader(); reader.Read(input, out var diagnostic); - diagnostic.Errors.ShouldBeEquivalentTo(new List() { + diagnostic.Errors.Should().BeEquivalentTo(new List() { new OpenApiError(new OpenApiReaderException("Expected a value.") { Pointer = "#line=4" }) diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyConverterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyConverterTests.cs index 8989459e5..7ee8c3439 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyConverterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyConverterTests.cs @@ -31,10 +31,10 @@ public void ParseObjectAsAnyShouldSucceed() yamlStream.Load(new StringReader(input)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); var anyMap = node.CreateAny(); @@ -74,7 +74,7 @@ public void ParseObjectAsAnyShouldSucceed() diagnostic.Errors.Should().BeEmpty(); - anyMap.ShouldBeEquivalentTo( + anyMap.Should().BeEquivalentTo( new OpenApiObject { ["aString"] = new OpenApiString("fooBar"), @@ -84,7 +84,7 @@ public void ParseObjectAsAnyShouldSucceed() ["aDate"] = new OpenApiDate(DateTimeOffset.Parse("2017-01-02", CultureInfo.InvariantCulture).Date), }); } - + [Fact] public void ParseNestedObjectAsAnyShouldSucceed() @@ -117,10 +117,10 @@ public void ParseNestedObjectAsAnyShouldSucceed() yamlStream.Load(new StringReader(input)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); var anyMap = node.CreateAny(); @@ -214,7 +214,7 @@ public void ParseNestedObjectAsAnyShouldSucceed() diagnostic.Errors.Should().BeEmpty(); - anyMap.ShouldBeEquivalentTo( + anyMap.Should().BeEquivalentTo( new OpenApiObject { ["aString"] = new OpenApiString("fooBar"), @@ -264,7 +264,7 @@ public void ParseNestedObjectAsAnyShouldSucceed() ["aDateTime"] = new OpenApiDateTime(DateTimeOffset.Parse("2017-01-01", CultureInfo.InvariantCulture)) }); } - + [Fact] public void ParseNestedObjectAsAnyWithPartialSchemaShouldSucceed() @@ -297,10 +297,10 @@ public void ParseNestedObjectAsAnyWithPartialSchemaShouldSucceed() yamlStream.Load(new StringReader(input)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); var anyMap = node.CreateAny(); @@ -370,7 +370,7 @@ public void ParseNestedObjectAsAnyWithPartialSchemaShouldSucceed() diagnostic.Errors.Should().BeEmpty(); - anyMap.ShouldBeEquivalentTo( + anyMap.Should().BeEquivalentTo( new OpenApiObject { ["aString"] = new OpenApiString("fooBar"), @@ -452,10 +452,10 @@ public void ParseNestedObjectAsAnyWithoutUsingSchemaShouldSucceed() yamlStream.Load(new StringReader(input)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); var anyMap = node.CreateAny(); @@ -463,7 +463,7 @@ public void ParseNestedObjectAsAnyWithoutUsingSchemaShouldSucceed() diagnostic.Errors.Should().BeEmpty(); - anyMap.ShouldBeEquivalentTo( + anyMap.Should().BeEquivalentTo( new OpenApiObject { ["aString"] = new OpenApiString("fooBar"), diff --git a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyTests.cs b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyTests.cs index 024f1f9f0..263c28fec 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ParseNodes/OpenApiAnyTests.cs @@ -27,16 +27,16 @@ public void ParseMapAsAnyShouldSucceed() yamlStream.Load(new StringReader(input)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); var anyMap = node.CreateAny(); diagnostic.Errors.Should().BeEmpty(); - anyMap.ShouldBeEquivalentTo( + anyMap.Should().BeEquivalentTo( new OpenApiObject { ["aString"] = new OpenApiString("fooBar"), @@ -59,16 +59,16 @@ public void ParseListAsAnyShouldSucceed() yamlStream.Load(new StringReader(input)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new ListNode(context, diagnostic, (YamlSequenceNode)yamlNode); + var node = new ListNode(context, (YamlSequenceNode)yamlNode); var any = node.CreateAny(); diagnostic.Errors.Should().BeEmpty(); - any.ShouldBeEquivalentTo( + any.Should().BeEquivalentTo( new OpenApiArray { new OpenApiString("fooBar"), @@ -88,16 +88,16 @@ public void ParseScalarIntegerAsAnyShouldSucceed() yamlStream.Load(new StringReader(input)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new ValueNode(context, diagnostic, (YamlScalarNode)yamlNode); + var node = new ValueNode(context, (YamlScalarNode)yamlNode); var any = node.CreateAny(); diagnostic.Errors.Should().BeEmpty(); - any.ShouldBeEquivalentTo( + any.Should().BeEquivalentTo( new OpenApiString("10") ); } @@ -112,18 +112,18 @@ public void ParseScalarDateTimeAsAnyShouldSucceed() yamlStream.Load(new StringReader(input)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new ValueNode(context, diagnostic, (YamlScalarNode)yamlNode); + var node = new ValueNode(context, (YamlScalarNode)yamlNode); var any = node.CreateAny(); diagnostic.Errors.Should().BeEmpty(); - any.ShouldBeEquivalentTo( + any.Should().BeEquivalentTo( new OpenApiString("2012-07-23T12:33:00") ); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs index e5290a693..e374dc205 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV2Tests.cs @@ -101,4 +101,4 @@ public void ParseSecuritySchemeReference() reference.Id.Should().Be(id); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs index 4f34ed562..6acf70500 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/ConvertToOpenApiReferenceV3Tests.cs @@ -101,4 +101,4 @@ public void ParseSecuritySchemeReference() reference.Id.Should().Be(id); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index d47f7dddc..d7f110b10 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs @@ -41,7 +41,7 @@ public void LoadSchemaReference() var referencedObject = document.ResolveReference(reference); // Assert - referencedObject.ShouldBeEquivalentTo( + referencedObject.Should().BeEquivalentTo( new OpenApiSchema { Required = @@ -96,7 +96,7 @@ public void LoadParameterReference() var referencedObject = document.ResolveReference(reference); // Assert - referencedObject.ShouldBeEquivalentTo( + referencedObject.Should().BeEquivalentTo( new OpenApiParameter { Name = "skip", @@ -139,7 +139,7 @@ public void LoadSecuritySchemeReference() var referencedObject = document.ResolveReference(reference); // Assert - referencedObject.ShouldBeEquivalentTo( + referencedObject.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.ApiKey, @@ -176,7 +176,7 @@ public void LoadResponseReference() var referencedObject = document.ResolveReference(reference); // Assert - referencedObject.ShouldBeEquivalentTo( + referencedObject.Should().BeEquivalentTo( new OpenApiResponse { Description = "Entity not found.", @@ -185,7 +185,7 @@ public void LoadResponseReference() Type = ReferenceType.Response, Id = "NotFound" }, - Content = new Dictionary + Content = new Dictionary { ["application/json"] = new OpenApiMediaType() } @@ -215,7 +215,7 @@ public void LoadResponseAndSchemaReference() var referencedObject = document.ResolveReference(reference); // Assert - referencedObject.ShouldBeEquivalentTo( + referencedObject.Should().BeEquivalentTo( new OpenApiResponse { Description = "General Error", @@ -255,4 +255,4 @@ public void LoadResponseAndSchemaReference() ); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs index e3dbfa19b..88866fd95 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/TestCustomExtension.cs @@ -36,7 +36,7 @@ public void ParseCustomExtension() }; var reader = new OpenApiStringReader(settings); - + var diag = new OpenApiDiagnostic(); var doc = reader.Read(description, out diag); diff --git a/test/Microsoft.OpenApi.Readers.Tests/TestHelper.cs b/test/Microsoft.OpenApi.Readers.Tests/TestHelper.cs index 0124d1a7f..c97e35e9b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/TestHelper.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/TestHelper.cs @@ -16,10 +16,9 @@ public static MapNode CreateYamlMapNode(Stream stream) yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); - var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(new OpenApiDiagnostic()); - return new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + return new MapNode(context, (YamlMappingNode)yamlNode); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs index 6e5538723..1ae3678d6 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs @@ -22,12 +22,12 @@ public void EquivalentV2AndV3DocumentsShouldProductEquivalentObjects(string file using (var streamV3 = Resources.GetStream(Path.Combine(SampleFolderPath, $"{fileName}.v3.yaml"))) { var openApiDocV2 = new OpenApiStreamReader().Read(streamV2, out var diagnosticV2); - var openApiDocV3 = new OpenApiStreamReader().Read(streamV3, out var diagnosticV3 ); + var openApiDocV3 = new OpenApiStreamReader().Read(streamV3, out var diagnosticV3); - openApiDocV3.ShouldBeEquivalentTo(openApiDocV2); + openApiDocV3.Should().BeEquivalentTo(openApiDocV2); - diagnosticV2.Errors.ShouldBeEquivalentTo(diagnosticV3.Errors); + diagnosticV2.Errors.Should().BeEquivalentTo(diagnosticV3.Errors); } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index fbf3da7de..1499dbed7 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -38,7 +38,7 @@ public void ShouldThrowWhenReferenceTypeIsInvalid() var reader = new OpenApiStringReader(); var doc = reader.Read(input, out var diagnostic); - diagnostic.Errors.ShouldBeEquivalentTo(new List { + diagnostic.Errors.Should().BeEquivalentTo(new List { new OpenApiError( new OpenApiException("Unknown reference type 'defi888nition'")) }); doc.Should().NotBeNull(); } @@ -66,7 +66,7 @@ public void ShouldThrowWhenReferenceDoesNotExist() var doc = reader.Read(input, out var diagnostic); - diagnostic.Errors.ShouldBeEquivalentTo(new List { + diagnostic.Errors.Should().BeEquivalentTo(new List { new OpenApiError( new OpenApiException("Invalid Reference identifier 'doesnotexist'.")) }); doc.Should().NotBeNull(); } @@ -102,7 +102,7 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) paths: {}", out var context); - openApiDoc.ShouldBeEquivalentTo( + openApiDoc.Should().BeEquivalentTo( new OpenApiDocument { Info = new OpenApiInfo @@ -143,7 +143,7 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) Paths = new OpenApiPaths() }); - context.ShouldBeEquivalentTo( + context.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 }); } @@ -155,23 +155,25 @@ public void ShouldParseProducesInAnyOrder() var reader = new OpenApiStreamReader(); var doc = reader.Read(stream, out var diagnostic); - Assert.NotNull(doc.Paths["/items"]); - Assert.Equal(3, doc.Paths["/items"].Operations.Count); - var successSchema = new OpenApiSchema() { Type = "array", + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "Item" + }, Items = new OpenApiSchema() { - Properties = new Dictionary() - { - { "id", new OpenApiSchema() - { - Type = "string", - Description = "Item identifier." - } - } - }, + //Properties = new Dictionary() + // { + // { "id", new OpenApiSchema() + // { + // Type = "string", + // Description = "Item identifier." + // } + // } + // }, Reference = new OpenApiReference() { Type = ReferenceType.Schema, @@ -180,67 +182,168 @@ public void ShouldParseProducesInAnyOrder() } }; - var errorSchema = new OpenApiSchema() + var okSchema = new OpenApiSchema() { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = "Item" + }, Properties = new Dictionary() - { - { "code", new OpenApiSchema() - { - Type = "integer", - Format = "int32" - } - }, - { "message", new OpenApiSchema() - { - Type = "string" - } - }, - { "fields", new OpenApiSchema() - { - Type = "string" - } - } - }, - Reference = new OpenApiReference() + { + { "id", new OpenApiSchema() + { + Type = "string", + Description = "Item identifier." + } + } + } + }; + + var errorSchema = new OpenApiSchema() + { + Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "Error" - } + }, + Properties = new Dictionary() + { + { "code", new OpenApiSchema() + { + Type = "integer", + Format = "int32" + } + }, + { "message", new OpenApiSchema() + { + Type = "string" + } + }, + { "fields", new OpenApiSchema() + { + Type = "string" + } + } + } }; - foreach (var operation in doc.Paths["/items"].Operations) + var okMediaType = new OpenApiMediaType { - Assert.Equal(2, operation.Value.Responses.Count); - - var okResponse = operation.Value.Responses["200"]; - okResponse.ShouldBeEquivalentTo( - new OpenApiResponse() - { - Description = "An OK response", - Content = GetMediaTypes(successSchema, operation.Key != OperationType.Post) - }); + Schema = new OpenApiSchema + { + Type = "array", + Items = okSchema + } + }; - var errorResponse = operation.Value.Responses["default"]; - errorResponse.ShouldBeEquivalentTo( - new OpenApiResponse() - { - Description = "An error response", - Content = GetMediaTypes(errorSchema, operation.Key != OperationType.Post) - }); - } + var errorMediaType = new OpenApiMediaType + { + Schema = errorSchema + }; - IDictionary GetMediaTypes(OpenApiSchema schema, bool includeXml) + doc.Should().BeEquivalentTo(new OpenApiDocument { - var mediaTypes = new Dictionary + Info = new OpenApiInfo + { + Title = "Two responses", + Version = "1.0.0" + }, + Servers = + { + new OpenApiServer + { + Url = "https://" + } + }, + Paths = new OpenApiPaths { - ["application/json"] = new OpenApiMediaType() { Schema = schema } - }; - if (includeXml) + ["/items"] = new OpenApiPathItem + { + Operations = + { + [OperationType.Get] = new OpenApiOperation + { + Responses = + { + ["200"] = new OpenApiResponse + { + Description = "An OK response", + Content = + { + ["application/json"] = okMediaType, + ["application/xml"] = okMediaType, + } + }, + ["default"] = new OpenApiResponse + { + Description = "An error response", + Content = + { + ["application/json"] = errorMediaType, + ["application/xml"] = errorMediaType + } + } + } + }, + [OperationType.Post] = new OpenApiOperation + { + Responses = + { + ["200"] = new OpenApiResponse + { + Description = "An OK response", + Content = + { + ["html/text"] = okMediaType + } + }, + ["default"] = new OpenApiResponse + { + Description = "An error response", + Content = + { + ["html/text"] = errorMediaType + } + } + } + }, + [OperationType.Patch] = new OpenApiOperation + { + Responses = + { + ["200"] = new OpenApiResponse + { + Description = "An OK response", + Content = + { + ["application/json"] = okMediaType, + ["application/xml"] = okMediaType, + } + }, + ["default"] = new OpenApiResponse + { + Description = "An error response", + Content = + { + ["application/json"] = errorMediaType, + ["application/xml"] = errorMediaType + } + } + } + } + } + } + }, + Components = new OpenApiComponents { - mediaTypes["application/xml"] = new OpenApiMediaType() { Schema = schema }; + Schemas = + { + ["Item"] = okSchema, + ["Error"] = errorSchema + } } - return mediaTypes; - } + }); } } @@ -309,12 +412,31 @@ public void ShouldAssignSchemaToAllResponses() var json = response.Value.Content["application/json"]; Assert.NotNull(json); - json.Schema.ShouldBeEquivalentTo(targetSchema); + json.Schema.Should().BeEquivalentTo(targetSchema); var xml = response.Value.Content["application/xml"]; Assert.NotNull(xml); - xml.Schema.ShouldBeEquivalentTo(targetSchema); + xml.Schema.Should().BeEquivalentTo(targetSchema); + } + } + + + [Fact] + public void ShouldAllowComponentsThatJustContainAReference() + { + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "ComponentRootReference.json"))) + { + OpenApiStreamReader reader = new OpenApiStreamReader(); + OpenApiDocument doc = reader.Read(stream, out OpenApiDiagnostic diags); + OpenApiSchema schema1 = doc.Components.Schemas["AllPets"]; + Assert.False(schema1.UnresolvedReference); + OpenApiSchema schema2 = (OpenApiSchema)doc.ResolveReference(schema1.Reference); + if (schema2.UnresolvedReference && schema1.Reference.Id == schema2.Reference.Id) + { + // detected a cycle - this code gets triggered + Assert.True(false, "A cycle should not be detected"); + } } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs index 010d763e4..7a98c7a6d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs @@ -31,7 +31,7 @@ public void ParseHeaderWithDefaultShouldSucceed() var header = OpenApiV2Deserializer.LoadHeader(node); // Assert - header.ShouldBeEquivalentTo( + header.Should().BeEquivalentTo( new OpenApiHeader { Schema = new OpenApiSchema() @@ -57,7 +57,7 @@ public void ParseHeaderWithEnumShouldSucceed() var header = OpenApiV2Deserializer.LoadHeader(node); // Assert - header.ShouldBeEquivalentTo( + header.Should().BeEquivalentTo( new OpenApiHeader { Schema = new OpenApiSchema() @@ -74,4 +74,4 @@ public void ParseHeaderWithEnumShouldSucceed() }); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs index 2a927c867..83620aa29 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs @@ -220,7 +220,7 @@ public void ParseBasicOperationShouldSucceed() var operation = OpenApiV2Deserializer.LoadOperation(node); // Assert - operation.ShouldBeEquivalentTo(_basicOperation); + operation.Should().BeEquivalentTo(_basicOperation); } [Fact] @@ -238,7 +238,7 @@ public void ParseBasicOperationTwiceShouldYieldSameObject() var operation = OpenApiV2Deserializer.LoadOperation(node); // Assert - operation.ShouldBeEquivalentTo(_basicOperation); + operation.Should().BeEquivalentTo(_basicOperation); } [Fact] @@ -255,7 +255,7 @@ public void ParseOperationWithFormDataShouldSucceed() var operation = OpenApiV2Deserializer.LoadOperation(node); // Assert - operation.ShouldBeEquivalentTo(_operationWithFormData); + operation.Should().BeEquivalentTo(_operationWithFormData); } [Fact] @@ -273,7 +273,7 @@ public void ParseOperationWithFormDataTwiceShouldYieldSameObject() var operation = OpenApiV2Deserializer.LoadOperation(node); // Assert - operation.ShouldBeEquivalentTo(_operationWithFormData); + operation.Should().BeEquivalentTo(_operationWithFormData); } [Fact] @@ -290,7 +290,7 @@ public void ParseOperationWithBodyShouldSucceed() var operation = OpenApiV2Deserializer.LoadOperation(node); // Assert - operation.ShouldBeEquivalentTo(_operationWithBody); + operation.Should().BeEquivalentTo(_operationWithBody); } [Fact] @@ -308,7 +308,7 @@ public void ParseOperationWithBodyTwiceShouldYieldSameObject() var operation = OpenApiV2Deserializer.LoadOperation(node); // Assert - operation.ShouldBeEquivalentTo(_operationWithBody); + operation.Should().BeEquivalentTo(_operationWithBody); } [Fact] @@ -325,7 +325,7 @@ public void ParseOperationWithResponseExamplesShouldSucceed() var operation = OpenApiV2Deserializer.LoadOperation(node); // Assert - operation.ShouldBeEquivalentTo( + operation.Should().BeEquivalentTo( new OpenApiOperation() { Responses = new OpenApiResponses() @@ -352,6 +352,18 @@ public void ParseOperationWithResponseExamplesShouldSucceed() new OpenApiFloat(6), new OpenApiFloat(7), } + }, + ["application/xml"] = new OpenApiMediaType() + { + Schema = new OpenApiSchema() + { + Type = "array", + Items = new OpenApiSchema() + { + Type = "number", + Format = "float" + } + } } } }} @@ -360,4 +372,4 @@ public void ParseOperationWithResponseExamplesShouldSucceed() ); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs index cebe8af1e..fc4e84f50 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs @@ -50,7 +50,7 @@ public void ParsePathParameterShouldSucceed() var parameter = OpenApiV2Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = ParameterLocation.Path, @@ -78,7 +78,7 @@ public void ParseQueryParameterShouldSucceed() var parameter = OpenApiV2Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = ParameterLocation.Query, @@ -131,7 +131,7 @@ public void ParseHeaderParameterShouldSucceed() var parameter = OpenApiV2Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = ParameterLocation.Header, @@ -183,7 +183,7 @@ public void ParseHeaderParameterWithIncorrectDataTypeShouldSucceed() var parameter = OpenApiV2Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = ParameterLocation.Header, @@ -235,7 +235,7 @@ public void ParseParameterWithNullLocationShouldSucceed() var parameter = OpenApiV2Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = null, @@ -263,7 +263,7 @@ public void ParseParameterWithNoLocationShouldSucceed() var parameter = OpenApiV2Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = null, @@ -291,7 +291,7 @@ public void ParseParameterWithNoSchemaShouldSucceed() var parameter = OpenApiV2Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = null, @@ -315,7 +315,7 @@ public void ParseParameterWithUnknownLocationShouldSucceed() var parameter = OpenApiV2Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = null, @@ -343,7 +343,7 @@ public void ParseParameterWithDefaultShouldSucceed() var parameter = OpenApiV2Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = ParameterLocation.Path, @@ -373,7 +373,7 @@ public void ParseParameterWithEnumShouldSucceed() var parameter = OpenApiV2Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = ParameterLocation.Path, @@ -394,4 +394,4 @@ public void ParseParameterWithEnumShouldSucceed() }); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs index 7f0d0f02d..5d3331207 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs @@ -256,7 +256,7 @@ public void ParseBasicPathItemWithFormDataShouldSucceed() var operation = OpenApiV2Deserializer.LoadPathItem(node); // Assert - operation.ShouldBeEquivalentTo(_basicPathItemWithFormData); + operation.Should().BeEquivalentTo(_basicPathItemWithFormData); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs index f82db3b82..9a75e5c8d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs @@ -31,7 +31,7 @@ public void ParseSchemaWithDefaultShouldSucceed() var schema = OpenApiV2Deserializer.LoadSchema(node); // Assert - schema.ShouldBeEquivalentTo( + schema.Should().BeEquivalentTo( new OpenApiSchema { Type = "number", @@ -54,7 +54,7 @@ public void ParseSchemaWithExampleShouldSucceed() var schema = OpenApiV2Deserializer.LoadSchema(node); // Assert - schema.ShouldBeEquivalentTo( + schema.Should().BeEquivalentTo( new OpenApiSchema { Type = "number", @@ -77,7 +77,7 @@ public void ParseSchemaWithEnumShouldSucceed() var schema = OpenApiV2Deserializer.LoadSchema(node); // Assert - schema.ShouldBeEquivalentTo( + schema.Should().BeEquivalentTo( new OpenApiSchema { Type = "number", @@ -91,4 +91,4 @@ public void ParseSchemaWithEnumShouldSucceed() }); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs index 29e04d05f..1a4a2a3d7 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs @@ -23,18 +23,18 @@ public void ParseHttpSecuritySchemeShouldSucceed() { using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicSecurityScheme.yaml"))) { - var document = OpenApiStreamReader.LoadYamlDocument(stream); + var document = LoadYamlDocument(stream); - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)document.RootNode); + var node = new MapNode(context, (YamlMappingNode)document.RootNode); // Act var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); // Assert - securityScheme.ShouldBeEquivalentTo( + securityScheme.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, @@ -48,17 +48,17 @@ public void ParseApiKeySecuritySchemeShouldSucceed() { using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "apiKeySecurityScheme.yaml"))) { - var document = OpenApiStreamReader.LoadYamlDocument(stream); - var context = new ParsingContext(); + var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)document.RootNode); + var node = new MapNode(context, (YamlMappingNode)document.RootNode); // Act var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); // Assert - securityScheme.ShouldBeEquivalentTo( + securityScheme.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.ApiKey, @@ -73,17 +73,17 @@ public void ParseOAuth2ImplicitSecuritySchemeShouldSucceed() { using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "oauth2ImplicitSecurityScheme.yaml"))) { - var document = OpenApiStreamReader.LoadYamlDocument(stream); - var context = new ParsingContext(); + var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)document.RootNode); + var node = new MapNode(context, (YamlMappingNode)document.RootNode); // Act var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); // Assert - securityScheme.ShouldBeEquivalentTo( + securityScheme.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, @@ -108,17 +108,17 @@ public void ParseOAuth2PasswordSecuritySchemeShouldSucceed() { using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "oauth2PasswordSecurityScheme.yaml"))) { - var document = OpenApiStreamReader.LoadYamlDocument(stream); - var context = new ParsingContext(); + var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)document.RootNode); + var node = new MapNode(context, (YamlMappingNode)document.RootNode); // Act var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); // Assert - securityScheme.ShouldBeEquivalentTo( + securityScheme.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, @@ -143,17 +143,17 @@ public void ParseOAuth2ApplicationSecuritySchemeShouldSucceed() { using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "oauth2ApplicationSecurityScheme.yaml"))) { - var document = OpenApiStreamReader.LoadYamlDocument(stream); - var context = new ParsingContext(); + var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)document.RootNode); + var node = new MapNode(context, (YamlMappingNode)document.RootNode); // Act var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); // Assert - securityScheme.ShouldBeEquivalentTo( + securityScheme.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, @@ -178,18 +178,18 @@ public void ParseOAuth2AccessCodeSecuritySchemeShouldSucceed() { using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "oauth2AccessCodeSecurityScheme.yaml"))) { - var document = OpenApiStreamReader.LoadYamlDocument(stream); + var document = LoadYamlDocument(stream); - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)document.RootNode); + var node = new MapNode(context, (YamlMappingNode)document.RootNode); // Act var securityScheme = OpenApiV2Deserializer.LoadSecurityScheme(node); // Assert - securityScheme.ShouldBeEquivalentTo( + securityScheme.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, @@ -208,5 +208,15 @@ public void ParseOAuth2AccessCodeSecuritySchemeShouldSucceed() }); } } + + static YamlDocument LoadYamlDocument(Stream input) + { + using (var reader = new StreamReader(input)) + { + var yamlStream = new YamlStream(); + yamlStream.Load(reader); + return yamlStream.Documents.First(); + } + } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs index 292bc716f..c87b491ab 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiServerTests.cs @@ -22,7 +22,8 @@ public void NoServer() version: 1.0.0 paths: {} "; - var reader = new OpenApiStringReader(new OpenApiReaderSettings() { + var reader = new OpenApiStringReader(new OpenApiReaderSettings() + { }); var doc = reader.Read(input, out var diagnostic); @@ -274,12 +275,12 @@ public void InvalidHostShouldYieldError() "; var reader = new OpenApiStringReader(new OpenApiReaderSettings() { -BaseUrl = new Uri("https://bing.com") + BaseUrl = new Uri("https://bing.com") }); var doc = reader.Read(input, out var diagnostic); doc.Servers.Count.Should().Be(0); - diagnostic.ShouldBeEquivalentTo( + diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic { Errors = diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/ComponentRootReference.json b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/ComponentRootReference.json new file mode 100644 index 000000000..c5c055e0d --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/ComponentRootReference.json @@ -0,0 +1,178 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore", + "license": { + "name": "MIT" + } + }, + "servers": [ + { + "url": "http://petstore.swagger.io/v1" + } + ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "tags": [ + "pets" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "How many items to return at one time (max 100)", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "A paged array of pets", + "headers": { + "x-next": { + "description": "A link to the next page of responses", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AllPets" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "tags": [ + "pets" + ], + "responses": { + "201": { + "description": "Null response" + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/pets/{petId}": { + "get": { + "summary": "Info for a specific pet", + "operationId": "showPetById", + "tags": [ + "pets" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "description": "The id of the pet to retrieve", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Expected response to a valid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pets" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Pet": { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "Pets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + }, + "AllPets": { + "$ref": "#/components/schemas/Pets" + }, + "Error": { + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithResponseExamples.yaml b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithResponseExamples.yaml index a03b04d80..725ec5c37 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithResponseExamples.yaml +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/OpenApiOperation/operationWithResponseExamples.yaml @@ -1,5 +1,6 @@ produces: - application/json + - application/xml responses: 200: description: An array of float response diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/twoResponses.json b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/twoResponses.json index a4c9510a3..f822543e1 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/twoResponses.json +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/Samples/twoResponses.json @@ -78,7 +78,7 @@ } }, "produces": [ - "application/json" + "html/text" ], "definitions": { "Item": { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs index 0bc8639d0..f23bee9f9 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs @@ -28,18 +28,18 @@ public void ParseBasicCallbackShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var callback = OpenApiV3Deserializer.LoadCallback(node); // Assert - diagnostic.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - callback.ShouldBeEquivalentTo( + callback.Should().BeEquivalentTo( new OpenApiCallback { PathItems = @@ -88,13 +88,13 @@ public void ParseCallbackWithReferenceShouldSucceed() var callback = subscribeOperation.Callbacks["simpleHook"]; - diagnostic.ShouldBeEquivalentTo( + diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); - callback.ShouldBeEquivalentTo( + callback.Should().BeEquivalentTo( new OpenApiCallback { - PathItems = + PathItems = { [RuntimeExpression.Build("$request.body#/url")]= new OpenApiPathItem { Operations = { @@ -135,21 +135,21 @@ public void ParseCallbackWithReferenceShouldSucceed() [Fact] public void ParseMultipleCallbacksWithReferenceShouldSucceed() { - using ( var stream = Resources.GetStream( Path.Combine( SampleFolderPath, "multipleCallbacksWithReference.yaml" ) ) ) + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleCallbacksWithReference.yaml"))) { // Act - var openApiDoc = new OpenApiStreamReader().Read( stream, out var diagnostic ); + var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); // Assert var path = openApiDoc.Paths.First().Value; var subscribeOperation = path.Operations[OperationType.Post]; - diagnostic.ShouldBeEquivalentTo( - new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 } ); + diagnostic.Should().BeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); var callback1 = subscribeOperation.Callbacks["simpleHook"]; - callback1.ShouldBeEquivalentTo( + callback1.Should().BeEquivalentTo( new OpenApiCallback { PathItems = @@ -186,10 +186,10 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() Type = ReferenceType.Callback, Id = "simpleHook", } - } ); + }); var callback2 = subscribeOperation.Callbacks["callback2"]; - callback2.ShouldBeEquivalentTo( + callback2.Should().BeEquivalentTo( new OpenApiCallback { PathItems = @@ -222,10 +222,10 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() }, } } - } ); + }); var callback3 = subscribeOperation.Callbacks["callback3"]; - callback3.ShouldBeEquivalentTo( + callback3.Should().BeEquivalentTo( new OpenApiCallback { PathItems = @@ -265,8 +265,8 @@ public void ParseMultipleCallbacksWithReferenceShouldSucceed() } } } - } ); + }); } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDiscriminatorTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDiscriminatorTests.cs index 70c903dd4..0768592b3 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDiscriminatorTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDiscriminatorTests.cs @@ -26,16 +26,16 @@ public void ParseBasicDiscriminatorShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var discriminator = OpenApiV3Deserializer.LoadDiscriminator(node); // Assert - discriminator.ShouldBeEquivalentTo( + discriminator.Should().BeEquivalentTo( new OpenApiDiscriminator { PropertyName = "pet_type", @@ -48,4 +48,4 @@ public void ParseBasicDiscriminatorShouldSucceed() } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index ce940e2d7..4888abebe 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -42,7 +42,7 @@ public void ParseDocumentFromInlineStringShouldSucceed() paths: {}", out var context); - openApiDoc.ShouldBeEquivalentTo( + openApiDoc.Should().BeEquivalentTo( new OpenApiDocument { Info = new OpenApiInfo @@ -53,7 +53,7 @@ public void ParseDocumentFromInlineStringShouldSucceed() Paths = new OpenApiPaths() }); - context.ShouldBeEquivalentTo( + context.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } @@ -88,7 +88,7 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) paths: {}", out var context); - openApiDoc.ShouldBeEquivalentTo( + openApiDoc.Should().BeEquivalentTo( new OpenApiDocument { Info = new OpenApiInfo @@ -125,7 +125,7 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) Paths = new OpenApiPaths() }); - context.ShouldBeEquivalentTo( + context.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } @@ -136,10 +136,10 @@ public void ParseBasicDocumentWithMultipleServersShouldSucceed() { var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - diagnostic.ShouldBeEquivalentTo( + diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); - openApiDoc.ShouldBeEquivalentTo( + openApiDoc.Should().BeEquivalentTo( new OpenApiDocument { Info = new OpenApiInfo @@ -172,7 +172,7 @@ public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() { var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - openApiDoc.ShouldBeEquivalentTo( + openApiDoc.Should().BeEquivalentTo( new OpenApiDocument { Info = new OpenApiInfo @@ -182,7 +182,7 @@ public void ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() Paths = new OpenApiPaths() }); - diagnostic.ShouldBeEquivalentTo( + diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic { Errors = @@ -201,7 +201,7 @@ public void ParseMinimalDocumentShouldSucceed() { var openApiDoc = new OpenApiStreamReader().Read(stream, out var diagnostic); - openApiDoc.ShouldBeEquivalentTo( + openApiDoc.Should().BeEquivalentTo( new OpenApiDocument { Info = new OpenApiInfo @@ -212,7 +212,7 @@ public void ParseMinimalDocumentShouldSucceed() Paths = new OpenApiPaths() }); - diagnostic.ShouldBeEquivalentTo( + diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } } @@ -637,10 +637,10 @@ public void ParseStandardPetStoreDocumentShouldSucceed() Components = components }; - actual.ShouldBeEquivalentTo(expected); + actual.Should().BeEquivalentTo(expected); } - context.ShouldBeEquivalentTo( + context.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } @@ -1170,10 +1170,10 @@ public void ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() } }; - actual.ShouldBeEquivalentTo(expected); + actual.Should().BeEquivalentTo(expected); } - context.ShouldBeEquivalentTo( + context.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } @@ -1189,7 +1189,7 @@ public void ParsePetStoreExpandedShouldSucceed() // TODO: Create the object in memory and compare with the one read from YAML file. } - context.ShouldBeEquivalentTo( + context.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } @@ -1215,7 +1215,7 @@ public void HeaderParameterShouldAllowExample() var exampleHeader = openApiDoc.Components?.Headers?["example-header"]; Assert.NotNull(exampleHeader); - exampleHeader.ShouldBeEquivalentTo( + exampleHeader.Should().BeEquivalentTo( new OpenApiHeader() { Description = "Test header with example", @@ -1240,7 +1240,7 @@ public void HeaderParameterShouldAllowExample() var examplesHeader = openApiDoc.Components?.Headers?["examples-header"]; Assert.NotNull(examplesHeader); - examplesHeader.ShouldBeEquivalentTo( + examplesHeader.Should().BeEquivalentTo( new OpenApiHeader() { Description = "Test header with example", @@ -1277,4 +1277,4 @@ public void HeaderParameterShouldAllowExample() } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs index 24f8af6a3..7f33491ff 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs @@ -26,16 +26,16 @@ public void ParseBasicEncodingShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var encoding = OpenApiV3Deserializer.LoadEncoding(node); // Assert - encoding.ShouldBeEquivalentTo( + encoding.Should().BeEquivalentTo( new OpenApiEncoding { ContentType = "application/xml; charset=utf-8" @@ -52,16 +52,16 @@ public void ParseAdvancedEncodingShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var encoding = OpenApiV3Deserializer.LoadEncoding(node); // Assert - encoding.ShouldBeEquivalentTo( + encoding.Should().BeEquivalentTo( new OpenApiEncoding { ContentType = "image/png, image/jpeg", @@ -81,4 +81,4 @@ public void ParseAdvancedEncodingShouldSucceed() } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs index 20e3f33eb..ead84f201 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs @@ -27,16 +27,16 @@ public void ParseAdvancedExampleShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); var example = OpenApiV3Deserializer.LoadExample(node); diagnostic.Errors.Should().BeEmpty(); - example.ShouldBeEquivalentTo( + example.Should().BeEquivalentTo( new OpenApiExample { Value = new OpenApiObject @@ -75,5 +75,15 @@ public void ParseAdvancedExampleShouldSucceed() }); } } + + [Fact] + public void ParseExampleForcedStringSucceed() + { + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "explicitString.yaml"))) + { + new OpenApiStreamReader().Read(stream, out var diagnostic); + diagnostic.Errors.Should().BeEmpty(); + } + } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs index c9e53f8c9..29b23cb31 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs @@ -28,16 +28,16 @@ public void ParseAdvancedInfoShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var openApiInfo = OpenApiV3Deserializer.LoadInfo(node); // Assert - openApiInfo.ShouldBeEquivalentTo( + openApiInfo.Should().BeEquivalentTo( new OpenApiInfo { Title = "Advanced Info", @@ -56,7 +56,7 @@ public void ParseAdvancedInfoShouldSucceed() }, License = new OpenApiLicense { - Extensions = {["x-disclaimer"] = new OpenApiString("Sample Extension String Disclaimer")}, + Extensions = { ["x-disclaimer"] = new OpenApiString("Sample Extension String Disclaimer") }, Name = "licenseName", Url = new Uri("http://www.example.com/url2") }, @@ -71,8 +71,8 @@ public void ParseAdvancedInfoShouldSucceed() }, ["x-list"] = new OpenApiArray { - new OpenApiInteger(1), - new OpenApiInteger(2) + new OpenApiString("1"), + new OpenApiString("2") } } }); @@ -88,16 +88,16 @@ public void ParseBasicInfoShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var openApiInfo = OpenApiV3Deserializer.LoadInfo(node); // Assert - openApiInfo.ShouldBeEquivalentTo( + openApiInfo.Should().BeEquivalentTo( new OpenApiInfo { Title = "Basic Info", @@ -128,16 +128,16 @@ public void ParseMinimalInfoShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var openApiInfo = OpenApiV3Deserializer.LoadInfo(node); // Assert - openApiInfo.ShouldBeEquivalentTo( + openApiInfo.Should().BeEquivalentTo( new OpenApiInfo { Title = "Minimal Info", @@ -146,4 +146,4 @@ public void ParseMinimalInfoShouldSucceed() } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs index c3ad268fb..e62eabb53 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs @@ -30,7 +30,7 @@ public void ParseMediaTypeWithExampleShouldSucceed() var mediaType = OpenApiV3Deserializer.LoadMediaType(node); // Assert - mediaType.ShouldBeEquivalentTo( + mediaType.Should().BeEquivalentTo( new OpenApiMediaType { Example = new OpenApiFloat(5), @@ -56,7 +56,7 @@ public void ParseMediaTypeWithExamplesShouldSucceed() var mediaType = OpenApiV3Deserializer.LoadMediaType(node); // Assert - mediaType.ShouldBeEquivalentTo( + mediaType.Should().BeEquivalentTo( new OpenApiMediaType { Examples = @@ -78,4 +78,4 @@ public void ParseMediaTypeWithExamplesShouldSucceed() }); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs index 564692c66..a74c64154 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs @@ -33,7 +33,7 @@ public void ParseOperationWithParameterWithNoLocationShouldSucceed() { // Arrange MapNode node; - using ( var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithParameterWithNoLocation.json")) ) + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithParameterWithNoLocation.json"))) { node = TestHelper.CreateYamlMapNode(stream); } @@ -42,7 +42,7 @@ public void ParseOperationWithParameterWithNoLocationShouldSucceed() var operation = OpenApiV3Deserializer.LoadOperation(node); // Assert - operation.ShouldBeEquivalentTo(new OpenApiOperation() + operation.Should().BeEquivalentTo(new OpenApiOperation() { Tags = { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs index 2c65a6e3c..44ba3316d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs @@ -30,7 +30,7 @@ public void ParsePathParameterShouldSucceed() var parameter = OpenApiV3Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = ParameterLocation.Path, @@ -58,7 +58,7 @@ public void ParseQueryParameterShouldSucceed() var parameter = OpenApiV3Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = ParameterLocation.Query, @@ -83,7 +83,7 @@ public void ParseQueryParameterWithObjectTypeShouldSucceed() { // Arrange MapNode node; - using ( var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "queryParameterWithObjectType.yaml")) ) + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "queryParameterWithObjectType.yaml"))) { node = TestHelper.CreateYamlMapNode(stream); } @@ -92,7 +92,7 @@ public void ParseQueryParameterWithObjectTypeShouldSucceed() var parameter = OpenApiV3Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = ParameterLocation.Query, @@ -114,7 +114,7 @@ public void ParseQueryParameterWithObjectTypeAndContentShouldSucceed() { // Arrange MapNode node; - using ( var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "queryParameterWithObjectTypeAndContent.yaml")) ) + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "queryParameterWithObjectTypeAndContent.yaml"))) { node = TestHelper.CreateYamlMapNode(stream); } @@ -123,7 +123,7 @@ public void ParseQueryParameterWithObjectTypeAndContentShouldSucceed() var parameter = OpenApiV3Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = ParameterLocation.Query, @@ -171,7 +171,7 @@ public void ParseHeaderParameterShouldSucceed() var parameter = OpenApiV3Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = ParameterLocation.Header, @@ -179,7 +179,7 @@ public void ParseHeaderParameterShouldSucceed() Description = "token to be passed as a header", Required = true, Style = ParameterStyle.Simple, - + Schema = new OpenApiSchema { Type = "array", @@ -197,7 +197,7 @@ public void ParseParameterWithNullLocationShouldSucceed() { // Arrange MapNode node; - using ( var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNullLocation.yaml")) ) + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNullLocation.yaml"))) { node = TestHelper.CreateYamlMapNode(stream); } @@ -206,7 +206,7 @@ public void ParseParameterWithNullLocationShouldSucceed() var parameter = OpenApiV3Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = null, @@ -225,7 +225,7 @@ public void ParseParameterWithNoLocationShouldSucceed() { // Arrange MapNode node; - using ( var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNoLocation.yaml")) ) + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNoLocation.yaml"))) { node = TestHelper.CreateYamlMapNode(stream); } @@ -234,7 +234,7 @@ public void ParseParameterWithNoLocationShouldSucceed() var parameter = OpenApiV3Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = null, @@ -253,7 +253,7 @@ public void ParseParameterWithUnknownLocationShouldSucceed() { // Arrange MapNode node; - using ( var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithUnknownLocation.yaml")) ) + using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithUnknownLocation.yaml"))) { node = TestHelper.CreateYamlMapNode(stream); } @@ -262,7 +262,7 @@ public void ParseParameterWithUnknownLocationShouldSucceed() var parameter = OpenApiV3Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = null, @@ -290,7 +290,7 @@ public void ParseParameterWithExampleShouldSucceed() var parameter = OpenApiV3Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = null, @@ -320,7 +320,7 @@ public void ParseParameterWithExamplesShouldSucceed() var parameter = OpenApiV3Deserializer.LoadParameter(node); // Assert - parameter.ShouldBeEquivalentTo( + parameter.Should().BeEquivalentTo( new OpenApiParameter { In = null, @@ -346,4 +346,4 @@ public void ParseParameterWithExamplesShouldSucceed() }); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index 289a9a94f..dbf0cf3f6 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -28,18 +28,18 @@ public void ParsePrimitiveSchemaShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var schema = OpenApiV3Deserializer.LoadSchema(node); // Assert - diagnostic.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - schema.ShouldBeEquivalentTo( + schema.Should().BeEquivalentTo( new OpenApiSchema { Type = "string", @@ -60,9 +60,9 @@ public void ParsePrimitiveSchemaFragmentShouldSucceed() var schema = reader.ReadFragment(stream, OpenApiSpecVersion.OpenApi3_0, out diagnostic); // Assert - diagnostic.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - schema.ShouldBeEquivalentTo( + schema.Should().BeEquivalentTo( new OpenApiSchema { Type = "string", @@ -87,9 +87,9 @@ public void ParsePrimitiveStringSchemaFragmentShouldSucceed() var schema = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); // Assert - diagnostic.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - schema.ShouldBeEquivalentTo( + schema.Should().BeEquivalentTo( new OpenApiSchema { Type = "integer", @@ -113,13 +113,13 @@ public void ParseExampleStringFragmentShouldSucceed() var openApiAny = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); // Assert - diagnostic.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - openApiAny.ShouldBeEquivalentTo( + openApiAny.Should().BeEquivalentTo( new OpenApiObject { ["foo"] = new OpenApiString("bar"), - ["baz"] = new OpenApiArray() { + ["baz"] = new OpenApiArray() { new OpenApiInteger(1), new OpenApiInteger(2) } @@ -141,9 +141,9 @@ public void ParseEnumFragmentShouldSucceed() var openApiAny = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); // Assert - diagnostic.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - openApiAny.ShouldBeEquivalentTo( + openApiAny.Should().BeEquivalentTo( new OpenApiArray { new OpenApiString("foo"), @@ -160,18 +160,18 @@ public void ParseSimpleSchemaShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var schema = OpenApiV3Deserializer.LoadSchema(node); // Assert - diagnostic.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - schema.ShouldBeEquivalentTo( + schema.Should().BeEquivalentTo( new OpenApiSchema { Type = "object", @@ -196,7 +196,7 @@ public void ParseSimpleSchemaShouldSucceed() Minimum = 0 } }, - AdditionalPropertiesAllowed = false + AdditionalPropertiesAllowed = false }); } } @@ -218,9 +218,9 @@ public void ParsePathFragmentShouldSucceed() var openApiAny = reader.ReadFragment(input, OpenApiSpecVersion.OpenApi3_0, out diagnostic); // Assert - diagnostic.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - openApiAny.ShouldBeEquivalentTo( + openApiAny.Should().BeEquivalentTo( new OpenApiPathItem { Summary = "externally referenced path item", @@ -230,8 +230,9 @@ public void ParsePathFragmentShouldSucceed() { Responses = new OpenApiResponses { - ["200"] = new OpenApiResponse { - Description = "Ok" + ["200"] = new OpenApiResponse + { + Description = "Ok" } } } @@ -248,18 +249,18 @@ public void ParseDictionarySchemaShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var schema = OpenApiV3Deserializer.LoadSchema(node); // Assert - diagnostic.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - schema.ShouldBeEquivalentTo( + schema.Should().BeEquivalentTo( new OpenApiSchema { Type = "object", @@ -280,18 +281,18 @@ public void ParseBasicSchemaWithExampleShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var schema = OpenApiV3Deserializer.LoadSchema(node); // Assert - diagnostic.ShouldBeEquivalentTo(new OpenApiDiagnostic()); + diagnostic.Should().BeEquivalentTo(new OpenApiDiagnostic()); - schema.ShouldBeEquivalentTo( + schema.Should().BeEquivalentTo( new OpenApiSchema { Type = "object", @@ -331,10 +332,10 @@ public void ParseBasicSchemaWithReferenceShouldSucceed() // Assert var components = openApiDoc.Components; - diagnostic.ShouldBeEquivalentTo( + diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); - components.ShouldBeEquivalentTo( + components.Should().BeEquivalentTo( new OpenApiComponents { Schemas = @@ -434,10 +435,10 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() // Assert var components = openApiDoc.Components; - diagnostic.ShouldBeEquivalentTo( + diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); - components.ShouldBeEquivalentTo( + components.Should().BeEquivalentTo( new OpenApiComponents { Schemas = @@ -464,7 +465,7 @@ public void ParseAdvancedSchemaWithReferenceShouldSucceed() { "name", "petType" - }, + }, Reference = new OpenApiReference() { Id= "Pet", @@ -610,7 +611,7 @@ public void ParseSelfReferencingSchemaShouldNotStackOverflow() // Assert var components = openApiDoc.Components; - diagnostic.ShouldBeEquivalentTo( + diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); var schemaExtension = new OpenApiSchema() @@ -642,8 +643,8 @@ public void ParseSelfReferencingSchemaShouldNotStackOverflow() schemaExtension.AllOf[0].Properties["child"] = schemaExtension; - components.Schemas["microsoft.graph.schemaExtension"].ShouldBeEquivalentTo(components.Schemas["microsoft.graph.schemaExtension"].AllOf[0].Properties["child"]); + components.Schemas["microsoft.graph.schemaExtension"].Should().BeEquivalentTo(components.Schemas["microsoft.graph.schemaExtension"].AllOf[0].Properties["child"]); } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs index 0765d7272..57c156cc0 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs @@ -27,16 +27,16 @@ public void ParseHttpSecuritySchemeShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var securityScheme = OpenApiV3Deserializer.LoadSecurityScheme(node); // Assert - securityScheme.ShouldBeEquivalentTo( + securityScheme.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, @@ -54,16 +54,16 @@ public void ParseApiKeySecuritySchemeShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var securityScheme = OpenApiV3Deserializer.LoadSecurityScheme(node); // Assert - securityScheme.ShouldBeEquivalentTo( + securityScheme.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.ApiKey, @@ -82,16 +82,16 @@ public void ParseBearerSecuritySchemeShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var securityScheme = OpenApiV3Deserializer.LoadSecurityScheme(node); // Assert - securityScheme.ShouldBeEquivalentTo( + securityScheme.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, @@ -110,16 +110,16 @@ public void ParseOAuth2SecuritySchemeShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var securityScheme = OpenApiV3Deserializer.LoadSecurityScheme(node); // Assert - securityScheme.ShouldBeEquivalentTo( + securityScheme.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, @@ -148,16 +148,16 @@ public void ParseOpenIdConnectSecuritySchemeShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var securityScheme = OpenApiV3Deserializer.LoadSecurityScheme(node); // Assert - securityScheme.ShouldBeEquivalentTo( + securityScheme.Should().BeEquivalentTo( new OpenApiSecurityScheme { Type = SecuritySchemeType.OpenIdConnect, @@ -167,4 +167,4 @@ public void ParseOpenIdConnectSecuritySchemeShouldSucceed() } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs index 15218ac77..a10d674a9 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs @@ -27,16 +27,16 @@ public void ParseBasicXmlShouldSucceed() yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents.First().RootNode; - var context = new ParsingContext(); var diagnostic = new OpenApiDiagnostic(); + var context = new ParsingContext(diagnostic); - var node = new MapNode(context, diagnostic, (YamlMappingNode)yamlNode); + var node = new MapNode(context, (YamlMappingNode)yamlNode); // Act var xml = OpenApiV3Deserializer.LoadXml(node); // Assert - xml.ShouldBeEquivalentTo( + xml.Should().BeEquivalentTo( new OpenApiXml { Name = "name1", @@ -47,4 +47,4 @@ public void ParseBasicXmlShouldSucceed() } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiExample/explicitString.yaml b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiExample/explicitString.yaml new file mode 100644 index 000000000..c3103a810 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiExample/explicitString.yaml @@ -0,0 +1,36 @@ +openapi: 3.0.1 +info: + version: 1.0.0 + title: Test API +paths: + /test-path: + post: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/test-schema' + responses: + default: + description: '' +components: + schemas: + test-schema: + type: object + properties: + sub: + $ref: '#/components/schemas/test-sub-schema' + example: + sub: + test-property1: "12345" + test-property2: "1970-01-01T00:00:00Z" + test-sub-schema: + type: object + properties: + test-property1: + type: string + example: "12345" + test-property2: + type: string + format: date-time + example: "1970-01-01T00:00:00Z" \ No newline at end of file diff --git a/test/Microsoft.OpenApi.SmokeTests/ApiGurus.cs b/test/Microsoft.OpenApi.SmokeTests/ApiGurus.cs index 7c8418115..e424512ac 100644 --- a/test/Microsoft.OpenApi.SmokeTests/ApiGurus.cs +++ b/test/Microsoft.OpenApi.SmokeTests/ApiGurus.cs @@ -19,7 +19,7 @@ public class ApisGuruTests { private static HttpClient _httpClient; private readonly ITestOutputHelper _output; - + public ApisGuruTests(ITestOutputHelper output) { _output = output; @@ -42,22 +42,22 @@ public static IEnumerable GetSchemas() .GetStringAsync("https://api.apis.guru/v2/list.json") .GetAwaiter().GetResult(); - var json = JObject.Parse(listJsonStr); - foreach (var item in json.Properties()) + var json = JObject.Parse(listJsonStr); + foreach (var item in json.Properties()) + { + var versions = GetProp(item.Value, "versions") as JObject; + if (versions == null) + continue; + foreach (var prop in versions.Properties()) { - var versions = GetProp(item.Value, "versions") as JObject; - if (versions == null) - continue; - foreach (var prop in versions.Properties()) - { - var urlToJson = GetProp(prop.Value, "swaggerUrl")?.ToObject(); - if (urlToJson != null) - yield return new object[] { urlToJson }; + var urlToJson = GetProp(prop.Value, "swaggerUrl")?.ToObject(); + if (urlToJson != null) + yield return new object[] { urlToJson }; - var utlToYaml = GetProp(prop.Value, "swaggerYamlUrl")?.ToObject(); - if (utlToYaml != null) - yield return new object[] { utlToYaml }; - } + var utlToYaml = GetProp(prop.Value, "swaggerYamlUrl")?.ToObject(); + if (utlToYaml != null) + yield return new object[] { utlToYaml }; + } } JToken GetProp(JToken obj, string prop) @@ -71,9 +71,9 @@ JToken GetProp(JToken obj, string prop) } // Disable as some APIs are currently invalid [Theory(DisplayName = "APIs.guru")] - [MemberData(nameof(GetSchemas))] + // [MemberData(nameof(GetSchemas))] public async Task EnsureThatICouldParse(string url) - { + { var response = await _httpClient.GetAsync(url); if (!response.IsSuccessStatusCode) { @@ -94,12 +94,12 @@ public async Task EnsureThatICouldParse(string url) { _output.WriteLine($"Errors parsing {url}"); _output.WriteLine(String.Join("\n", diagnostic.Errors)); - // Assert.True(false); // Uncomment to identify descriptions with errors. + // Assert.True(false); // Uncomment to identify descriptions with errors. } Assert.NotNull(openApiDocument); stopwatch.Stop(); - _output.WriteLine($"Parsing {url} took {stopwatch.ElapsedMilliseconds} ms."); + _output.WriteLine($"Parsing {url} took {stopwatch.ElapsedMilliseconds} ms."); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.SmokeTests/Microsoft.OpenApi.SmokeTests.csproj b/test/Microsoft.OpenApi.SmokeTests/Microsoft.OpenApi.SmokeTests.csproj index d62be8960..f9565bb64 100644 --- a/test/Microsoft.OpenApi.SmokeTests/Microsoft.OpenApi.SmokeTests.csproj +++ b/test/Microsoft.OpenApi.SmokeTests/Microsoft.OpenApi.SmokeTests.csproj @@ -9,10 +9,13 @@ - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/Microsoft.OpenApi.Tests/DefaultSettingsFixture.cs b/test/Microsoft.OpenApi.Tests/DefaultSettingsFixture.cs index 7cad2949a..9bb685ddc 100644 --- a/test/Microsoft.OpenApi.Tests/DefaultSettingsFixture.cs +++ b/test/Microsoft.OpenApi.Tests/DefaultSettingsFixture.cs @@ -25,4 +25,4 @@ public DefaultSettingsFixture() .WithStrictOrdering()); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/DefaultSettingsFixtureCollection.cs b/test/Microsoft.OpenApi.Tests/DefaultSettingsFixtureCollection.cs index 21180a7c5..4fdfe0b16 100644 --- a/test/Microsoft.OpenApi.Tests/DefaultSettingsFixtureCollection.cs +++ b/test/Microsoft.OpenApi.Tests/DefaultSettingsFixtureCollection.cs @@ -16,4 +16,4 @@ namespace Microsoft.OpenApi.Tests public class DefaultSettingsFixtureCollection : ICollectionFixture { } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs b/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs index a1b861ded..5c91249d3 100644 --- a/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs +++ b/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs @@ -186,9 +186,9 @@ public void CompositeRuntimeExpressionContainsMultipleExpressions() Assert.Equal(expression, response.Expression); var compositeExpression = runtimeExpression as CompositeExpression; - Assert.Equal(2,compositeExpression.ContainedExpressions.Count); + Assert.Equal(2, compositeExpression.ContainedExpressions.Count); - compositeExpression.ContainedExpressions.ShouldBeEquivalentTo(new List() + compositeExpression.ContainedExpressions.Should().BeEquivalentTo(new List() { new UrlExpression(), new RequestExpression(new HeaderExpression("foo")) @@ -232,7 +232,7 @@ public void CompositeRuntimeExpressionWithMultipleRuntimeExpressionsAndFakeBrace response.Expression.Should().Be(expression); var compositeExpression = runtimeExpression as CompositeExpression; - compositeExpression.ContainedExpressions.ShouldBeEquivalentTo(new List() + compositeExpression.ContainedExpressions.Should().BeEquivalentTo(new List() { new UrlExpression(), new RequestExpression(new HeaderExpression("foo")) @@ -248,7 +248,7 @@ public void CompositeRuntimeExpressionWithInvalidRuntimeExpressions(string expre Action test = () => RuntimeExpression.Build(expression); // Assert - test.ShouldThrow().WithMessage(String.Format(SRResource.RuntimeExpressionHasInvalidFormat, invalidExpression)); + test.Should().Throw().WithMessage(String.Format(SRResource.RuntimeExpressionHasInvalidFormat, invalidExpression)); } [Theory] diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 943d58c63..bcf61dda2 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -14,16 +14,18 @@ - - + + - + - + - + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs index ae006679c..fbc86e7f9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs @@ -203,4 +203,4 @@ public void SerializeReferencedCallbackAsV3JsonWithoutReferenceWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs index b06866087..002143b15 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs @@ -379,7 +379,7 @@ public void SerializeAdvancedComponentsWithReferenceAsJsonV3Works() // Act var actual = AdvancedComponentsWithReference.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -454,7 +454,7 @@ public void SerializeAdvancedComponentsWithReferenceAsYamlV3Works() // Act var actual = AdvancedComponentsWithReference.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -538,7 +538,7 @@ public void SerializeTopLevelReferencingComponentsAsYamlV3Works() // Act var actual = TopLevelReferencingComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -579,11 +579,11 @@ public void SerializeTopLevelSelfReferencingWithOtherPropertiesComponentsAsYamlV // Act var actual = TopLevelSelfReferencingComponentsWithOtherProperties.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs index 1441be7c5..1a99241d1 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs @@ -91,4 +91,4 @@ public void SerializeAdvanceContactAsYamlWorks(OpenApiSpecVersion version) actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs index 95a213920..ea65ec6eb 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs @@ -7,6 +7,7 @@ using System.IO; using FluentAssertions; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; using Xunit; @@ -885,6 +886,95 @@ public class OpenApiDocumentTests Components = AdvancedComponents }; + public static OpenApiDocument DuplicateExtensions = new OpenApiDocument + { + Info = new OpenApiInfo + { + Version = "1.0.0", + Title = "Swagger Petstore (Simple)", + Description = "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + }, + Servers = new List + { + new OpenApiServer + { + Url = "http://petstore.swagger.io/api" + } + }, + Paths = new OpenApiPaths + { + ["/add/{operand1}/{operand2}"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [OperationType.Get] = new OpenApiOperation + { + OperationId = "addByOperand1AndByOperand2", + Parameters = new List + { + new OpenApiParameter + { + Name = "operand1", + In = ParameterLocation.Path, + Description = "The first operand", + Required = true, + Schema = new OpenApiSchema + { + Type = "integer", + Extensions = new Dictionary + { + ["my-extension"] = new Any.OpenApiInteger(4), + } + }, + Extensions = new Dictionary + { + ["my-extension"] = new Any.OpenApiInteger(4), + } + }, + new OpenApiParameter + { + Name = "operand2", + In = ParameterLocation.Path, + Description = "The second operand", + Required = true, + Schema = new OpenApiSchema + { + Type = "integer", + Extensions = new Dictionary + { + ["my-extension"] = new Any.OpenApiInteger(4), + } + }, + Extensions = new Dictionary + { + ["my-extension"] = new Any.OpenApiInteger(4), + } + }, + }, + Responses = new OpenApiResponses + { + ["200"] = new OpenApiResponse + { + Description = "pet response", + Content = new Dictionary + { + ["application/json"] = new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "array", + Items = PetSchema + } + }, + } + } + } + } + } + } + } + }; + private readonly ITestOutputHelper _output; public OpenApiDocumentTests(ITestOutputHelper output) @@ -2148,7 +2238,186 @@ public void SerializeAdvancedDocumentAsV2JsonWorks() AdvancedDocument.SerializeAsV2(writer); writer.Flush(); var actual = outputStringWriter.GetStringBuilder().ToString(); - + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void SerializeDuplicateExtensionsAsV3JsonWorks() + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter); + var expected = @"{ + ""openapi"": ""3.0.1"", + ""info"": { + ""title"": ""Swagger Petstore (Simple)"", + ""description"": ""A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification"", + ""version"": ""1.0.0"" + }, + ""servers"": [ + { + ""url"": ""http://petstore.swagger.io/api"" + } + ], + ""paths"": { + ""/add/{operand1}/{operand2}"": { + ""get"": { + ""operationId"": ""addByOperand1AndByOperand2"", + ""parameters"": [ + { + ""name"": ""operand1"", + ""in"": ""path"", + ""description"": ""The first operand"", + ""required"": true, + ""schema"": { + ""type"": ""integer"", + ""my-extension"": 4 + }, + ""my-extension"": 4 + }, + { + ""name"": ""operand2"", + ""in"": ""path"", + ""description"": ""The second operand"", + ""required"": true, + ""schema"": { + ""type"": ""integer"", + ""my-extension"": 4 + }, + ""my-extension"": 4 + } + ], + ""responses"": { + ""200"": { + ""description"": ""pet response"", + ""content"": { + ""application/json"": { + ""schema"": { + ""type"": ""array"", + ""items"": { + ""required"": [ + ""id"", + ""name"" + ], + ""type"": ""object"", + ""properties"": { + ""id"": { + ""type"": ""integer"", + ""format"": ""int64"" + }, + ""name"": { + ""type"": ""string"" + }, + ""tag"": { + ""type"": ""string"" + } + } + } + } + } + } + } + } + } + } + } +}"; + + // Act + DuplicateExtensions.SerializeAsV3(writer); + writer.Flush(); + var actual = outputStringWriter.GetStringBuilder().ToString(); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void SerializeDuplicateExtensionsAsV2JsonWorks() + { + // Arrange + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter); + var expected = @"{ + ""swagger"": ""2.0"", + ""info"": { + ""title"": ""Swagger Petstore (Simple)"", + ""description"": ""A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification"", + ""version"": ""1.0.0"" + }, + ""host"": ""petstore.swagger.io"", + ""basePath"": ""/api"", + ""schemes"": [ + ""http"" + ], + ""paths"": { + ""/add/{operand1}/{operand2}"": { + ""get"": { + ""operationId"": ""addByOperand1AndByOperand2"", + ""produces"": [ + ""application/json"" + ], + ""parameters"": [ + { + ""in"": ""path"", + ""name"": ""operand1"", + ""description"": ""The first operand"", + ""required"": true, + ""type"": ""integer"", + ""my-extension"": 4 + }, + { + ""in"": ""path"", + ""name"": ""operand2"", + ""description"": ""The second operand"", + ""required"": true, + ""type"": ""integer"", + ""my-extension"": 4 + } + ], + ""responses"": { + ""200"": { + ""description"": ""pet response"", + ""schema"": { + ""type"": ""array"", + ""items"": { + ""required"": [ + ""id"", + ""name"" + ], + ""type"": ""object"", + ""properties"": { + ""id"": { + ""format"": ""int64"", + ""type"": ""integer"" + }, + ""name"": { + ""type"": ""string"" + }, + ""tag"": { + ""type"": ""string"" + } + } + } + } + } + } + } + } + } +}"; + + // Act + DuplicateExtensions.SerializeAsV2(writer); + writer.Flush(); + var actual = outputStringWriter.GetStringBuilder().ToString(); + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -2416,7 +2685,7 @@ public void SerializeAdvancedDocumentWithReferenceAsV2JsonWorks() AdvancedDocumentWithReference.SerializeAsV2(writer); writer.Flush(); var actual = outputStringWriter.GetStringBuilder().ToString(); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -2442,7 +2711,7 @@ public void SerializeSimpleDocumentWithTopLevelReferencingComponentsAsYamlV2Work // Act var actual = SimpleDocumentWithTopLevelReferencingComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -2462,7 +2731,7 @@ public void SerializeSimpleDocumentWithTopLevelSelfReferencingComponentsAsYamlV3 // Act var actual = SimpleDocumentWithTopLevelSelfReferencingComponents.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -2491,7 +2760,7 @@ public void SerializeSimpleDocumentWithTopLevelSelfReferencingWithOtherPropertie // Act var actual = SimpleDocumentWithTopLevelSelfReferencingComponentsWithOtherProperties.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -2552,5 +2821,97 @@ public void SerializeDocumentWithReferenceButNoComponents() // Assert Assert.NotEmpty(actual); } + + [Fact] + public void SerializeRelativePathAsV2JsonWorks() + { + // Arrange + var expected = + @"swagger: '2.0' +info: + version: 1.0.0 +basePath: /server1 +paths: { }"; + var doc = new OpenApiDocument() + { + Info = new OpenApiInfo() { Version = "1.0.0" }, + Servers = new List() { + new OpenApiServer() + { + Url = "/server1" + } + } + }; + + // Act + var actual = doc.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void SerializeRelativePathWithHostAsV2JsonWorks() + { + // Arrange + var expected = + @"swagger: '2.0' +info: + version: 1.0.0 +host: //example.org +basePath: /server1 +paths: { }"; + var doc = new OpenApiDocument() + { + Info = new OpenApiInfo() { Version = "1.0.0" }, + Servers = new List() { + new OpenApiServer() + { + Url = "//example.org/server1" + } + } + }; + + // Act + var actual = doc.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + + [Fact] + public void SerializeRelativeRootPathWithHostAsV2JsonWorks() + { + // Arrange + var expected = + @"swagger: '2.0' +info: + version: 1.0.0 +host: //example.org +paths: { }"; + var doc = new OpenApiDocument() + { + Info = new OpenApiInfo() { Version = "1.0.0" }, + Servers = new List() { + new OpenApiServer() + { + Url = "//example.org/" + } + } + }; + + // Act + var actual = doc.SerializeAsYaml(OpenApiSpecVersion.OpenApi2_0); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); + } + } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiEncodingTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiEncodingTests.cs index cec2bc58a..24bfca242 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiEncodingTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiEncodingTests.cs @@ -75,4 +75,4 @@ public void SerializeAdvanceEncodingAsV3YamlWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs index e93d36b85..896b96215 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs @@ -218,4 +218,4 @@ public void SerializeReferencedExampleAsV3JsonWithoutReferenceWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExternalDocsTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiExternalDocsTests.cs index 62c93925a..7d37fc9a4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiExternalDocsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExternalDocsTests.cs @@ -78,4 +78,4 @@ public void SerializeAdvanceExDocsAsV3YamlWorks() #endregion } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs index 3ec936b1d..5c2671e54 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs @@ -190,4 +190,4 @@ public void SerializeReferencedHeaderAsV2JsonWithoutReferenceWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs index e2301b3f0..b2395a9ed 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs @@ -37,7 +37,7 @@ public class OpenApiInfoTests public static IEnumerable BasicInfoJsonExpected() { - var specVersions = new[] {OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0}; + var specVersions = new[] { OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0 }; foreach (var specVersion in specVersions) { yield return new object[] @@ -66,7 +66,7 @@ public void SerializeBasicInfoAsJsonWorks(OpenApiSpecVersion version, string exp public static IEnumerable BasicInfoYamlExpected() { - var specVersions = new[] {OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0}; + var specVersions = new[] { OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0 }; foreach (var specVersion in specVersions) { yield return new object[] @@ -93,7 +93,7 @@ public void SerializeBasicInfoAsYamlWorks(OpenApiSpecVersion version, string exp public static IEnumerable AdvanceInfoJsonExpect() { - var specVersions = new[] {OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0}; + var specVersions = new[] { OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0 }; foreach (var specVersion in specVersions) { yield return new object[] @@ -136,7 +136,7 @@ public void SerializeAdvanceInfoAsJsonWorks(OpenApiSpecVersion version, string e public static IEnumerable AdvanceInfoYamlExpect() { - var specVersions = new[] {OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0}; + var specVersions = new[] { OpenApiSpecVersion.OpenApi3_0, OpenApiSpecVersion.OpenApi2_0 }; foreach (var specVersion in specVersions) { yield return new object[] @@ -196,4 +196,4 @@ public void InfoVersionShouldAcceptDateStyledAsVersions() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs index 888d247fe..52e99b0b4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs @@ -109,4 +109,4 @@ public void SerializeAdvanceLicenseAsYamlWorks(OpenApiSpecVersion version) actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs index 4fb92c006..ffcaa8804 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs @@ -162,4 +162,4 @@ public void SerializeReferencedLinkAsV3JsonWithoutReferenceWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs index da71c1acb..c59da1e86 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs @@ -407,4 +407,4 @@ public void SerializeMediaTypeWithObjectExamplesAsV3JsonWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowTests.cs index 0a8afe496..80040f566 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowTests.cs @@ -125,4 +125,4 @@ public void SerializeCompleteOAuthFlowAsV3JsonWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowsTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowsTests.cs index f90750d61..7d4882bbb 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowsTests.cs @@ -147,4 +147,4 @@ public void SerializeOAuthFlowsWithMultipleFlowsAsV3JsonWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs index 51ba8b08d..a59746214 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs @@ -359,6 +359,7 @@ public void SerializeOperationWithBodyAsV3JsonWorks() ""$ref"": ""#/components/responses/response1"" }, ""400"": { + ""description"": null, ""content"": { ""application/json"": { ""schema"": { @@ -431,6 +432,7 @@ public void SerializeAdvancedOperationWithTagAndSecurityAsV3JsonWorks() ""$ref"": ""#/components/responses/response1"" }, ""400"": { + ""description"": null, ""content"": { ""application/json"": { ""schema"": { @@ -554,7 +556,7 @@ public void SerializeOperationWithFormDataAsV3JsonWorks() // Act var actual = _operationWithFormData.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -607,7 +609,7 @@ public void SerializeOperationWithFormDataAsV2JsonWorks() // Act var actual = _operationWithFormData.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -658,6 +660,7 @@ public void SerializeOperationWithBodyAsV2JsonWorks() ""$ref"": ""#/responses/response1"" }, ""400"": { + ""description"": null, ""schema"": { ""maximum"": 10, ""minimum"": 5, @@ -672,7 +675,7 @@ public void SerializeOperationWithBodyAsV2JsonWorks() // Act var actual = _operationWithBody.SerializeAsJson(OpenApiSpecVersion.OpenApi2_0); - + // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); expected = expected.MakeLineBreaksEnvironmentNeutral(); @@ -727,6 +730,7 @@ public void SerializeAdvancedOperationWithTagAndSecurityAsV2JsonWorks() ""$ref"": ""#/responses/response1"" }, ""400"": { + ""description"": null, ""schema"": { ""maximum"": 10, ""minimum"": 5, @@ -779,4 +783,4 @@ public void SerializeOperationWithNullCollectionAsV2JsonWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs index 9833b5859..f37d18ed7 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs @@ -49,7 +49,7 @@ public class OpenApiParameterTests Title = "title2", Description = "description2" }, - Examples = new Dictionary + Examples = new Dictionary { ["test"] = new OpenApiExample { @@ -424,4 +424,4 @@ public void SerializeParameterWithFormStyleAndExplodeTrueWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs index 9e5b347ab..c251814db 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs @@ -65,7 +65,7 @@ public void SettingExternalReferenceShouldSucceed(string expected, string extern public void SerializeSchemaReferenceAsJsonV3Works() { // Arrange - var reference = new OpenApiReference {Type = ReferenceType.Schema, Id = "Pet"}; + var reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "Pet" }; var expected = @"{ ""$ref"": ""#/components/schemas/Pet"" }"; @@ -182,7 +182,7 @@ public void SerializeExternalReferenceAsYamlV2Works() public void SerializeExternalReferenceAsJsonV3Works() { // Arrange - var reference = new OpenApiReference {ExternalResource = "main.json", Id = "Pets"}; + var reference = new OpenApiReference { ExternalResource = "main.json", Id = "Pets" }; var expected = @"{ ""$ref"": ""main.json#/Pets"" @@ -201,7 +201,7 @@ public void SerializeExternalReferenceAsJsonV3Works() public void SerializeExternalReferenceAsYamlV3Works() { // Arrange - var reference = new OpenApiReference {ExternalResource = "main.json", Id = "Pets"}; + var reference = new OpenApiReference { ExternalResource = "main.json", Id = "Pets" }; var expected = @"$ref: main.json#/Pets"; // Act @@ -211,4 +211,4 @@ public void SerializeExternalReferenceAsYamlV3Works() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs index 0ad882e9e..b225417fc 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs @@ -140,4 +140,4 @@ public void SerializeReferencedRequestBodyAsV3JsonWithoutReferenceWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs index 98fab35f5..9b86a6d51 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System.Collections.Generic; using System.Globalization; using System.IO; using FluentAssertions; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; +using Microsoft.OpenApi.Interfaces; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; using Xunit; @@ -33,7 +35,11 @@ public class OpenApiResponseTests Reference = new OpenApiReference {Type = ReferenceType.Schema, Id = "customType"} } }, - Example = new OpenApiString("Blabla") + Example = new OpenApiString("Blabla"), + Extensions = new Dictionary + { + ["myextension"] = new OpenApiString("myextensionvalue"), + }, } }, Headers = @@ -116,11 +122,18 @@ public void SerializeBasicResponseWorks( OpenApiSpecVersion version, OpenApiFormat format) { - // Arrange & Act + // Arrange + var expected = format == OpenApiFormat.Json ? @"{ + ""description"": null +}" : @"description: "; + + // Act var actual = BasicResponse.Serialize(version, format); // Assert - actual.Should().Be("{ }"); + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + actual.Should().Be(expected); } [Fact] @@ -151,7 +164,8 @@ public void SerializeAdvancedResponseAsV3JsonWorks() ""$ref"": ""#/components/schemas/customType"" } }, - ""example"": ""Blabla"" + ""example"": ""Blabla"", + ""myextension"": ""myextensionvalue"" } } }"; @@ -186,7 +200,8 @@ public void SerializeAdvancedResponseAsV3YamlWorks() type: array items: $ref: '#/components/schemas/customType' - example: Blabla"; + example: Blabla + myextension: myextensionvalue"; // Act var actual = AdvancedResponse.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0); @@ -212,6 +227,7 @@ public void SerializeAdvancedResponseAsV2JsonWorks() ""examples"": { ""text/plain"": ""Blabla"" }, + ""myextension"": ""myextensionvalue"", ""headers"": { ""X-Rate-Limit-Limit"": { ""description"": ""The number of allowed requests in the current period"", @@ -245,6 +261,7 @@ public void SerializeAdvancedResponseAsV2YamlWorks() $ref: '#/definitions/customType' examples: text/plain: Blabla +myextension: myextensionvalue headers: X-Rate-Limit-Limit: description: The number of allowed requests in the current period @@ -390,4 +407,4 @@ public void SerializeReferencedResponseAsV2JsonWithoutReferenceWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 2015985e4..4f9510132 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -164,7 +164,7 @@ public class OpenApiSchemaTests public static OpenApiSchema AdvancedSchemaWithRequiredPropertiesObject = new OpenApiSchema { Title = "title1", - Required = new HashSet(){ "property1" }, + Required = new HashSet() { "property1" }, Properties = new Dictionary { ["property1"] = new OpenApiSchema @@ -478,4 +478,4 @@ public void SerializeSchemaWRequiredPropertiesAsV2JsonWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecurityRequirementTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecurityRequirementTests.cs index 134526ebb..7d630c5f6 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecurityRequirementTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecurityRequirementTests.cs @@ -21,7 +21,7 @@ public class OpenApiSecurityRequirementTests [ new OpenApiSecurityScheme { - Reference = new OpenApiReference {Type = ReferenceType.SecurityScheme, Id = "scheme1"} + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "scheme1" } } ] = new List { @@ -32,7 +32,7 @@ public class OpenApiSecurityRequirementTests [ new OpenApiSecurityScheme { - Reference = new OpenApiReference {Type = ReferenceType.SecurityScheme, Id = "scheme2"} + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "scheme2" } } ] = new List { @@ -42,7 +42,7 @@ public class OpenApiSecurityRequirementTests [ new OpenApiSecurityScheme { - Reference = new OpenApiReference {Type = ReferenceType.SecurityScheme, Id = "scheme3"} + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "scheme3" } } ] = new List() }; @@ -53,7 +53,7 @@ public class OpenApiSecurityRequirementTests [ new OpenApiSecurityScheme { - Reference = new OpenApiReference {Type = ReferenceType.SecurityScheme, Id = "scheme1"} + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "scheme1" } } ] = new List { @@ -75,7 +75,7 @@ public class OpenApiSecurityRequirementTests [ new OpenApiSecurityScheme { - Reference = new OpenApiReference {Type = ReferenceType.SecurityScheme, Id = "scheme3"} + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "scheme3" } } ] = new List() }; @@ -255,7 +255,7 @@ public void SchemesShouldConsiderOnlyReferenceIdForEquality() // Act securityRequirement.Add(securityScheme1, new List()); - securityRequirement.Add(securityScheme2, new List {"scope1", "scope2"}); + securityRequirement.Add(securityScheme2, new List { "scope1", "scope2" }); Action addSecurityScheme1Duplicate = () => securityRequirement.Add(securityScheme1Duplicate, new List()); @@ -265,19 +265,19 @@ public void SchemesShouldConsiderOnlyReferenceIdForEquality() // Assert // Only the first two should be added successfully since the latter two are duplicates of securityScheme1. // Duplicate determination only considers Reference.Id. - addSecurityScheme1Duplicate.ShouldThrow(); - addSecurityScheme1WithDifferentProperties.ShouldThrow(); + addSecurityScheme1Duplicate.Should().Throw(); + addSecurityScheme1WithDifferentProperties.Should().Throw(); securityRequirement.Should().HaveCount(2); - securityRequirement.ShouldBeEquivalentTo( + securityRequirement.Should().BeEquivalentTo( new OpenApiSecurityRequirement { // This should work with any security scheme object // as long as Reference.Id os securityScheme1 [securityScheme1WithDifferentProperties] = new List(), - [securityScheme2] = new List {"scope1", "scope2"}, + [securityScheme2] = new List { "scope1", "scope2" }, }); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs index 78b6b4b4b..5fb99cb95 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs @@ -348,4 +348,4 @@ public void SerializeReferencedSecuritySchemeAsV3JsonWithoutReferenceWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiServerTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiServerTests.cs index 5557d7e0e..e4d4f36fb 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiServerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiServerTests.cs @@ -101,4 +101,4 @@ public void SerializeAdvancedServerAsV3JsonWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiServerVariableTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiServerVariableTests.cs index 613c95971..9d50f76f6 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiServerVariableTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiServerVariableTests.cs @@ -82,4 +82,4 @@ public void SerializeAdvancedServerVariableAsV3YamlWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs index 30c5f28af..4920e165d 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs @@ -386,4 +386,4 @@ public void SerializeReferencedTagAsV2YamlWorks() actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs index 77c834042..9e79c5211 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs @@ -95,4 +95,4 @@ public void SerializeAdvancedXmlAsYamlWorks(OpenApiSpecVersion version) actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs index fba3965cf..45cc9c3d9 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiValidatorTests.cs @@ -56,7 +56,7 @@ public void ResponseMustHaveADescription() var walker = new OpenApiWalker(validator); walker.Walk(openApiDocument); - validator.Errors.ShouldBeEquivalentTo( + validator.Errors.Should().BeEquivalentTo( new List { new OpenApiValidatorError(nameof(OpenApiResponseRules.ResponseRequiredFields),"#/paths/~1test/get/responses/200/description", @@ -91,7 +91,7 @@ public void ServersShouldBeReferencedByIndex() var walker = new OpenApiWalker(validator); walker.Walk(openApiDocument); - validator.Errors.ShouldBeEquivalentTo( + validator.Errors.Should().BeEquivalentTo( new List { new OpenApiValidatorError(nameof(OpenApiServerRules.ServerRequiredFields), "#/servers/1/url", @@ -104,7 +104,7 @@ public void ServersShouldBeReferencedByIndex() public void ValidateCustomExtension() { var ruleset = ValidationRuleSet.GetDefaultRuleSet(); - + ruleset.Add( new ValidationRule( (context, item) => @@ -131,13 +131,13 @@ public void ValidateCustomExtension() Baz = "baz" }; - openApiDocument.Info.Extensions.Add("x-foo",fooExtension); + openApiDocument.Info.Extensions.Add("x-foo", fooExtension); var validator = new OpenApiValidator(ruleset); var walker = new OpenApiWalker(validator); walker.Walk(openApiDocument); - validator.Errors.ShouldBeEquivalentTo( + validator.Errors.Should().BeEquivalentTo( new List { new OpenApiValidatorError("FooExtensionRule", "#/info/x-foo", "Don't say hey") @@ -160,4 +160,4 @@ public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion) writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/StringExtensions.cs b/test/Microsoft.OpenApi.Tests/StringExtensions.cs index c2741035e..5b01c97ac 100644 --- a/test/Microsoft.OpenApi.Tests/StringExtensions.cs +++ b/test/Microsoft.OpenApi.Tests/StringExtensions.cs @@ -21,4 +21,4 @@ public static string MakeLineBreaksEnvironmentNeutral(this string input) .Replace("\n", Environment.NewLine); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs index 0edfc93a7..d10eaf590 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs @@ -20,7 +20,7 @@ public void ValidateKeyMustMatchRegularExpressionInComponents() { // Arrange const string key = "%@abc"; - + OpenApiComponents components = new OpenApiComponents() { Responses = new Dictionary @@ -33,7 +33,7 @@ public void ValidateKeyMustMatchRegularExpressionInComponents() // Act bool result = !errors.Any(); - + // Assert Assert.False(result); diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs index ac499708d..ec6bba7b5 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiContactValidationTests.cs @@ -19,7 +19,7 @@ public void ValidateEmailFieldIsEmailAddressInContact() { // Arrange const string testEmail = "support/example.com"; - + OpenApiContact contact = new OpenApiContact() { Email = testEmail diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs index 4c1325894..fee728f76 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiExternalDocsValidationTests.cs @@ -24,7 +24,7 @@ public void ValidateUrlIsRequiredInExternalDocs() var errors = externalDocs.Validate(ValidationRuleSet.GetDefaultRuleSet()); // Assert - + bool result = !errors.Any(); Assert.False(result); diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs index d6e30010b..3ed365c8d 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiReferenceValidationTests.cs @@ -44,7 +44,7 @@ public void ReferencedSchemaShouldOnlyBeValidatedOnce() { ["/"] = new OpenApiPathItem() { - Operations = new Dictionary + Operations = new Dictionary { [OperationType.Get] = new OpenApiOperation() { @@ -52,7 +52,7 @@ public void ReferencedSchemaShouldOnlyBeValidatedOnce() { ["200"] = new OpenApiResponse() { - Content = new Dictionary() + Content = new Dictionary() { ["application/json"] = new OpenApiMediaType() { @@ -67,11 +67,11 @@ public void ReferencedSchemaShouldOnlyBeValidatedOnce() }; // Act - var errors = document.Validate(new ValidationRuleSet() { new AlwaysFailRule()}); + var errors = document.Validate(new ValidationRuleSet() { new AlwaysFailRule() }); // Assert - Assert.True(errors.Count() == 1); + Assert.True(errors.Count() == 1); } [Fact] @@ -154,11 +154,11 @@ public void UnresolvedSchemaReferencedShouldNotBeValidated() } } - public class AlwaysFailRule : ValidationRule where T: IOpenApiElement + public class AlwaysFailRule : ValidationRule where T : IOpenApiElement { - public AlwaysFailRule() : base( (c,t) => c.CreateError("x","y")) + public AlwaysFailRule() : base((c, t) => c.CreateError("x", "y")) { - + } } } diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs index 1a93739f2..91b1643fa 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs @@ -261,7 +261,7 @@ public void ValidateSchemaRequiredFieldListMustContainThePropertySpecifiedInTheD // Assert result.Should().BeFalse(); - errors.ShouldAllBeEquivalentTo(new List + errors.Should().BeEquivalentTo(new List { new OpenApiValidatorError(nameof(OpenApiSchemaRules.ValidateSchemaDiscriminator),"#/schemas/schema1/discriminator", string.Format(SRResource.Validation_SchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator, diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs index a00f5e502..a039b39c2 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiTagValidationTests.cs @@ -50,7 +50,7 @@ public void ValidateExtensionNameStartsWithXDashInTag() var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); validator.Visit(tag as IOpenApiExtensible); errors = validator.Errors; - bool result = !errors.Any(); + bool result = !errors.Any(); // Assert Assert.False(result); diff --git a/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs b/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs index 725eacdaf..fc947da20 100644 --- a/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Walkers/WalkerLocationTests.cs @@ -23,7 +23,7 @@ public void LocateTopLevelObjects() var walker = new OpenApiWalker(locator); walker.Walk(doc); - locator.Locations.ShouldBeEquivalentTo(new List { + locator.Locations.Should().BeEquivalentTo(new List { "#/servers", "#/tags" }); @@ -49,7 +49,7 @@ public void LocateTopLevelArrayItems() var walker = new OpenApiWalker(locator); walker.Walk(doc); - locator.Locations.ShouldBeEquivalentTo(new List { + locator.Locations.Should().BeEquivalentTo(new List { "#/servers", "#/servers/0", "#/servers/1", @@ -96,7 +96,7 @@ public void LocatePathOperationContentSchema() var walker = new OpenApiWalker(locator); walker.Walk(doc); - locator.Locations.ShouldBeEquivalentTo(new List { + locator.Locations.Should().BeEquivalentTo(new List { "#/servers", "#/paths", "#/paths/~1test", @@ -111,7 +111,7 @@ public void LocatePathOperationContentSchema() }); - locator.Keys.ShouldBeEquivalentTo(new List { "/test", "Get", "200", "application/json" }); + locator.Keys.Should().BeEquivalentTo(new List { "/test", "Get", "200", "application/json" }); } [Fact] @@ -144,7 +144,7 @@ public void WalkDOMWithCycles() var walker = new OpenApiWalker(locator); walker.Walk(doc); - locator.Locations.ShouldBeEquivalentTo(new List { + locator.Locations.Should().BeEquivalentTo(new List { "#/servers", "#/paths", "#/components", @@ -211,7 +211,7 @@ public void LocateReferences() { ["application/json"] = new OpenApiMediaType() { - Schema = derivedSchema + Schema = derivedSchema } }, Headers = new Dictionary() @@ -226,13 +226,14 @@ public void LocateReferences() }, Components = new OpenApiComponents() { - Schemas = new Dictionary() { + Schemas = new Dictionary() + { ["derived"] = derivedSchema, ["base"] = baseSchema, }, Headers = new Dictionary() { - ["test-header"] = testHeader + ["test-header"] = testHeader } } }; @@ -241,7 +242,7 @@ public void LocateReferences() var walker = new OpenApiWalker(locator); walker.Walk(doc); - locator.Locations.Where(l => l.StartsWith("referenceAt:")).ShouldBeEquivalentTo(new List { + locator.Locations.Where(l => l.StartsWith("referenceAt:")).Should().BeEquivalentTo(new List { "referenceAt: #/paths/~1/get/responses/200/content/application~1json/schema", "referenceAt: #/paths/~1/get/responses/200/headers/test-header", "referenceAt: #/components/schemas/derived/anyOf/0", diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs index d8f827341..06d95c9ad 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs @@ -70,7 +70,7 @@ public void WriteStringListAsJsonShouldMatchExpected(string[] stringValues) JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new List(stringValues))); // Assert - parsedObject.ShouldBeEquivalentTo(expectedObject); + parsedObject.Should().BeEquivalentTo(expectedObject); } public static IEnumerable WriteMapAsJsonShouldMatchExpectedTestCasesSimple() @@ -196,10 +196,10 @@ public static IEnumerable WriteMapAsJsonShouldMatchExpectedTestCasesCo private void WriteValueRecursive(OpenApiJsonWriter writer, object value) { - if (value == null - || value.GetType().IsPrimitive - || value is decimal - || value is string + if (value == null + || value.GetType().IsPrimitive + || value is decimal + || value is string || value is DateTimeOffset || value is DateTime) { @@ -246,7 +246,7 @@ public void WriteMapAsJsonShouldMatchExpected(IDictionary inputM var expectedObject = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(inputMap)); // Assert - parsedObject.ShouldBeEquivalentTo(expectedObject); + parsedObject.Should().BeEquivalentTo(expectedObject); } public static IEnumerable WriteDateTimeAsJsonTestCases() @@ -293,4 +293,4 @@ public void WriteDateTimeAsJsonShouldMatchExpected(DateTimeOffset dateTimeOffset writtenString.Should().Be(expectedString); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs index eb51c9f3e..2ecf93f42 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs @@ -217,4 +217,4 @@ private static string WriteAsJson(IOpenApiAny any) return value.MakeLineBreaksEnvironmentNeutral(); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs index 477d64166..60e598882 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs @@ -76,4 +76,4 @@ public void WriteStringWithSpecialCharactersAsYamlWorks(string input, string exp actual.Should().Be(expected); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs index e6b81e0fb..a73fdbb9b 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.IO; using FluentAssertions; +using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; using Xunit; using Xunit.Abstractions; @@ -264,7 +265,7 @@ private void WriteValueRecursive(OpenApiYamlWriter writer, object value) } else if (value.GetType().IsGenericType && (typeof(IDictionary<,>).IsAssignableFrom(value.GetType().GetGenericTypeDefinition()) || - typeof(Dictionary<,>).IsAssignableFrom(value.GetType().GetGenericTypeDefinition()) ) ) + typeof(Dictionary<,>).IsAssignableFrom(value.GetType().GetGenericTypeDefinition()))) { writer.WriteStartObject(); foreach (var elementValue in (dynamic)(value)) @@ -346,5 +347,299 @@ public void WriteDateTimeAsJsonShouldMatchExpected(DateTimeOffset dateTimeOffset // Assert writtenString.Should().Be(expectedString); } + + [Fact] + + public void WriteInlineSchema() + { + // Arrange + var doc = CreateDocWithSimpleSchemaToInline(); + + var expected = +@"openapi: 3.0.1 +info: + title: Demo + version: 1.0.0 +paths: + /: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + type: object +components: { }"; + + var outputString = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences}); + + // Act + doc.SerializeAsV3(writer); + var actual = outputString.GetStringBuilder().ToString(); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } + + + + [Fact] + public void WriteInlineSchemaV2() + { + var doc = CreateDocWithSimpleSchemaToInline(); + + var expected = +@"swagger: '2.0' +info: + title: Demo + version: 1.0.0 +paths: + /: + get: + produces: + - application/json + responses: + '200': + description: OK + schema: + type: object"; + + var outputString = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences }); + + // Act + doc.SerializeAsV2(writer); + var actual = outputString.GetStringBuilder().ToString(); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } + + private static OpenApiDocument CreateDocWithSimpleSchemaToInline() + { + // Arrange + var thingSchema = new OpenApiSchema() + { + Type = "object", + UnresolvedReference = false, + Reference = new OpenApiReference + { + Id = "thing", + Type = ReferenceType.Schema + } + }; + + var doc = new OpenApiDocument() + { + Info = new OpenApiInfo() + { + Title = "Demo", + Version = "1.0.0" + }, + Paths = new OpenApiPaths() + { + ["/"] = new OpenApiPathItem + { + Operations = { + [OperationType.Get] = new OpenApiOperation() { + Responses = { + ["200"] = new OpenApiResponse { + Description = "OK", + Content = { + ["application/json"] = new OpenApiMediaType() { + Schema = thingSchema + } + } + } + } + } + } + } + }, + Components = new OpenApiComponents + { + Schemas = { + ["thing"] = thingSchema} + } + }; + return doc; + } + + [Fact] + + public void WriteInlineRecursiveSchema() + { + // Arrange + var doc = CreateDocWithRecursiveSchemaReference(); + + var expected = +@"openapi: 3.0.1 +info: + title: Demo + version: 1.0.0 +paths: + /: + get: + responses: + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + children: + $ref: '#/components/schemas/thing' + related: + type: integer +components: + schemas: + thing: + type: object + properties: + children: + type: object + properties: + children: + $ref: '#/components/schemas/thing' + related: + type: integer + related: + type: integer"; + // Component schemas that are there due to cycles are still inlined because the items they reference may not exist in the components because they don't have cycles. + + var outputString = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences }); + + // Act + doc.SerializeAsV3(writer); + var actual = outputString.GetStringBuilder().ToString(); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } + + private static OpenApiDocument CreateDocWithRecursiveSchemaReference() + { + var thingSchema = new OpenApiSchema() + { + Type = "object", + UnresolvedReference = false, + Reference = new OpenApiReference + { + Id = "thing", + Type = ReferenceType.Schema + } + }; + thingSchema.Properties["children"] = thingSchema; + + var relatedSchema = new OpenApiSchema() + { + Type = "integer", + UnresolvedReference = false, + Reference = new OpenApiReference + { + Id = "related", + Type = ReferenceType.Schema + } + }; + + thingSchema.Properties["related"] = relatedSchema; + + var doc = new OpenApiDocument() + { + Info = new OpenApiInfo() + { + Title = "Demo", + Version = "1.0.0" + }, + Paths = new OpenApiPaths() + { + ["/"] = new OpenApiPathItem + { + Operations = { + [OperationType.Get] = new OpenApiOperation() { + Responses = { + ["200"] = new OpenApiResponse { + Description = "OK", + Content = { + ["application/json"] = new OpenApiMediaType() { + Schema = thingSchema + } + } + } + } + } + } + } + }, + Components = new OpenApiComponents + { + Schemas = { + ["thing"] = thingSchema} + } + }; + return doc; + } + + [Fact] + public void WriteInlineRecursiveSchemav2() + { + // Arrange + var doc = CreateDocWithRecursiveSchemaReference(); + + var expected = +@"swagger: '2.0' +info: + title: Demo + version: 1.0.0 +paths: + /: + get: + produces: + - application/json + responses: + '200': + description: OK + schema: + type: object + properties: + children: + $ref: '#/definitions/thing' + related: + type: integer +definitions: + thing: + type: object + properties: + children: + $ref: '#/definitions/thing' + related: + $ref: '#/definitions/related' + related: + type: integer"; + // Component schemas that are there due to cycles are still inlined because the items they reference may not exist in the components because they don't have cycles. + + var outputString = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiYamlWriter(outputString, new OpenApiWriterSettings { ReferenceInline = ReferenceInlineSetting.InlineLocalReferences }); + + // Act + doc.SerializeAsV2(writer); + var actual = outputString.GetStringBuilder().ToString(); + + // Assert + actual = actual.MakeLineBreaksEnvironmentNeutral(); + expected = expected.MakeLineBreaksEnvironmentNeutral(); + Assert.Equal(expected, actual); + } + } -} \ No newline at end of file +}