diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs index 525a3f52f..f8cacc352 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiDocumentDeserializer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Interfaces; @@ -191,7 +192,7 @@ private static string BuildUrl(string scheme, string host, string basePath) { var pieces = host.Split(':'); host = pieces.First(); - port = int.Parse(pieces.Last()); + port = int.Parse(pieces.Last(), CultureInfo.InvariantCulture); } var uriBuilder = new UriBuilder() diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs index f23746a0a..b7f07c4cd 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiHeaderDeserializer.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System; +using System.Globalization; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; @@ -54,10 +55,10 @@ internal static partial class OpenApiV2Deserializer GetOrCreateSchema(o).Default = n.CreateAny(); } }, - { + { "maximum", (o, n) => { - GetOrCreateSchema(o).Maximum = decimal.Parse(n.GetScalarValue()); + GetOrCreateSchema(o).Maximum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { @@ -69,7 +70,7 @@ internal static partial class OpenApiV2Deserializer { "minimum", (o, n) => { - GetOrCreateSchema(o).Minimum = decimal.Parse(n.GetScalarValue()); + GetOrCreateSchema(o).Minimum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { @@ -81,13 +82,13 @@ internal static partial class OpenApiV2Deserializer { "maxLength", (o, n) => { - GetOrCreateSchema(o).MaxLength = int.Parse(n.GetScalarValue()); + GetOrCreateSchema(o).MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "minLength", (o, n) => { - GetOrCreateSchema(o).MinLength = int.Parse(n.GetScalarValue()); + GetOrCreateSchema(o).MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { @@ -99,13 +100,13 @@ internal static partial class OpenApiV2Deserializer { "maxItems", (o, n) => { - GetOrCreateSchema(o).MaxItems = int.Parse(n.GetScalarValue()); + GetOrCreateSchema(o).MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "minItems", (o, n) => { - GetOrCreateSchema(o).MinItems = int.Parse(n.GetScalarValue()); + GetOrCreateSchema(o).MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { @@ -117,7 +118,7 @@ internal static partial class OpenApiV2Deserializer { "multipleOf", (o, n) => { - GetOrCreateSchema(o).MultipleOf = decimal.Parse(n.GetScalarValue()); + GetOrCreateSchema(o).MultipleOf = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs index 12b033f73..baf8499cb 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiParameterDeserializer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; @@ -90,25 +91,25 @@ internal static partial class OpenApiV2Deserializer { "minimum", (o, n) => { - GetOrCreateSchema(o).Minimum = decimal.Parse(n.GetScalarValue()); + GetOrCreateSchema(o).Minimum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "maximum", (o, n) => { - GetOrCreateSchema(o).Maximum = decimal.Parse(n.GetScalarValue()); + GetOrCreateSchema(o).Maximum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "maxLength", (o, n) => { - GetOrCreateSchema(o).MaxLength = int.Parse(n.GetScalarValue()); + GetOrCreateSchema(o).MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "minLength", (o, n) => { - GetOrCreateSchema(o).MinLength = int.Parse(n.GetScalarValue()); + GetOrCreateSchema(o).MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { diff --git a/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs index 4d08dd29d..dd76a38cc 100644 --- a/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V2/OpenApiSchemaDeserializer.cs @@ -6,6 +6,7 @@ using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using System.Collections.Generic; +using System.Globalization; namespace Microsoft.OpenApi.Readers.V2 { @@ -26,13 +27,13 @@ internal static partial class OpenApiV2Deserializer { "multipleOf", (o, n) => { - o.MultipleOf = decimal.Parse(n.GetScalarValue()); + o.MultipleOf = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "maximum", (o, n) => { - o.Maximum = decimal.Parse(n.GetScalarValue()); + o.Maximum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { @@ -44,7 +45,7 @@ internal static partial class OpenApiV2Deserializer { "minimum", (o, n) => { - o.Minimum = decimal.Parse(n.GetScalarValue()); + o.Minimum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { @@ -56,13 +57,13 @@ internal static partial class OpenApiV2Deserializer { "maxLength", (o, n) => { - o.MaxLength = int.Parse(n.GetScalarValue()); + o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "minLength", (o, n) => { - o.MinLength = int.Parse(n.GetScalarValue()); + o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { @@ -74,13 +75,13 @@ internal static partial class OpenApiV2Deserializer { "maxItems", (o, n) => { - o.MaxItems = int.Parse(n.GetScalarValue()); + o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "minItems", (o, n) => { - o.MinItems = int.Parse(n.GetScalarValue()); + o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { @@ -92,13 +93,13 @@ internal static partial class OpenApiV2Deserializer { "maxProperties", (o, n) => { - o.MaxProperties = int.Parse(n.GetScalarValue()); + o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "minProperties", (o, n) => { - o.MinProperties = int.Parse(n.GetScalarValue()); + o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { diff --git a/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs b/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs index a86ebcb50..94d21f723 100644 --- a/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs +++ b/src/Microsoft.OpenApi.Readers/V3/OpenApiSchemaDeserializer.cs @@ -6,6 +6,7 @@ using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers.ParseNodes; using System.Collections.Generic; +using System.Globalization; namespace Microsoft.OpenApi.Readers.V3 { @@ -26,13 +27,13 @@ internal static partial class OpenApiV3Deserializer { "multipleOf", (o, n) => { - o.MultipleOf = decimal.Parse(n.GetScalarValue()); + o.MultipleOf = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "maximum", (o, n) => { - o.Maximum = decimal.Parse(n.GetScalarValue()); + o.Maximum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { @@ -44,7 +45,7 @@ internal static partial class OpenApiV3Deserializer { "minimum", (o, n) => { - o.Minimum = decimal.Parse(n.GetScalarValue()); + o.Minimum = decimal.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { @@ -56,13 +57,13 @@ internal static partial class OpenApiV3Deserializer { "maxLength", (o, n) => { - o.MaxLength = int.Parse(n.GetScalarValue()); + o.MaxLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "minLength", (o, n) => { - o.MinLength = int.Parse(n.GetScalarValue()); + o.MinLength = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { @@ -74,13 +75,13 @@ internal static partial class OpenApiV3Deserializer { "maxItems", (o, n) => { - o.MaxItems = int.Parse(n.GetScalarValue()); + o.MaxItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "minItems", (o, n) => { - o.MinItems = int.Parse(n.GetScalarValue()); + o.MinItems = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { @@ -92,13 +93,13 @@ internal static partial class OpenApiV3Deserializer { "maxProperties", (o, n) => { - o.MaxProperties = int.Parse(n.GetScalarValue()); + o.MaxProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { "minProperties", (o, n) => { - o.MinProperties = int.Parse(n.GetScalarValue()); + o.MinProperties = int.Parse(n.GetScalarValue(), CultureInfo.InvariantCulture); } }, { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index 6850628bd..5eaef365e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. using System.Collections.Generic; +using System.Globalization; +using System.Threading; using FluentAssertions; using Microsoft.OpenApi.Exceptions; using Microsoft.OpenApi.Models; @@ -66,5 +68,76 @@ public void ShouldThrowWhenReferenceDoesNotExist() new OpenApiError( new OpenApiException("Invalid Reference identifier 'doesnotexist'.")) }); doc.Should().NotBeNull(); } + + [Theory] + [InlineData("en-US")] + [InlineData("hi-IN")] + // The equivalent of English 1,000.36 in French and Danish is 1.000,36 + [InlineData("fr-FR")] + [InlineData("da-DK")] + public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) + { + Thread.CurrentThread.CurrentCulture = new CultureInfo(culture); + Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture); + + var openApiDoc = new OpenApiStringReader().Read( + @" +swagger: 2.0 +info: + title: Simple Document + version: 0.9.1 +definitions: + sampleSchema: + type: object + properties: + sampleProperty: + type: double + minimum: 100.54 + maximum: 60,000,000.35 + exclusiveMaximum: true + exclusiveMinimum: false +paths: {}", + out var context); + + openApiDoc.ShouldBeEquivalentTo( + new OpenApiDocument + { + Info = new OpenApiInfo + { + Title = "Simple Document", + Version = "0.9.1" + }, + Components = new OpenApiComponents() + { + Schemas = + { + ["sampleSchema"] = new OpenApiSchema() + { + Type = "object", + Properties = + { + ["sampleProperty"] = new OpenApiSchema() + { + Type = "double", + Minimum = (decimal)100.54, + Maximum = (decimal)60000000.35, + ExclusiveMaximum = true, + ExclusiveMinimum = false + } + }, + Reference = new OpenApiReference() + { + Id = "sampleSchema", + Type = ReferenceType.Schema + } + } + } + }, + Paths = new OpenApiPaths() + }); + + context.ShouldBeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 }); + } } } \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiAnyTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiAnyTests.cs index 86706f9dc..1891d86ba 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiAnyTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiAnyTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System; +using System.Globalization; using System.IO; using System.Linq; using FluentAssertions; @@ -43,7 +44,7 @@ public void ParseMapAsAnyShouldSucceed() ["aString"] = new OpenApiString("fooBar"), ["aInteger"] = new OpenApiInteger(10), ["aDouble"] = new OpenApiDouble(2.34), - ["aDateTime"] = new OpenApiDateTime(DateTimeOffset.Parse("2017-01-01")) + ["aDateTime"] = new OpenApiDateTime(DateTimeOffset.Parse("2017-01-01", CultureInfo.InvariantCulture)) }); } @@ -75,7 +76,7 @@ public void ParseListAsAnyShouldSucceed() new OpenApiString("fooBar"), new OpenApiInteger(10), new OpenApiDouble(2.34), - new OpenApiDateTime(DateTimeOffset.Parse("2017-01-01")) + new OpenApiDateTime(DateTimeOffset.Parse("2017-01-01", CultureInfo.InvariantCulture)) }); } @@ -123,7 +124,7 @@ public void ParseScalarDateTimeAsAnyShouldSucceed() diagnostic.Errors.Should().BeEmpty(); any.ShouldBeEquivalentTo( - new OpenApiDateTime(DateTimeOffset.Parse("2012-07-23T12:33:00")) + new OpenApiDateTime(DateTimeOffset.Parse("2012-07-23T12:33:00", CultureInfo.InvariantCulture)) ); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 8117e1c57..4a1f992b4 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; +using System.Threading; using FluentAssertions; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Validations; @@ -54,6 +56,78 @@ public void ParseDocumentFromInlineStringShouldSucceed() new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); } + [Theory] + [InlineData("en-US")] + [InlineData("hi-IN")] + // The equivalent of English 1,000.36 in French and Danish is 1.000,36 + [InlineData("fr-FR")] + [InlineData("da-DK")] + public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) + { + Thread.CurrentThread.CurrentCulture = new CultureInfo(culture); + Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture); + + var openApiDoc = new OpenApiStringReader().Read( + @" +openapi : 3.0.0 +info: + title: Simple Document + version: 0.9.1 +components: + schemas: + sampleSchema: + type: object + properties: + sampleProperty: + type: double + minimum: 100.54 + maximum: 60,000,000.35 + exclusiveMaximum: true + exclusiveMinimum: false +paths: {}", + out var context); + + openApiDoc.ShouldBeEquivalentTo( + new OpenApiDocument + { + Info = new OpenApiInfo + { + Title = "Simple Document", + Version = "0.9.1" + }, + Components = new OpenApiComponents() + { + Schemas = + { + ["sampleSchema"] = new OpenApiSchema() + { + Type = "object", + Properties = + { + ["sampleProperty"] = new OpenApiSchema() + { + Type = "double", + Minimum = (decimal)100.54, + Maximum = (decimal)60000000.35, + ExclusiveMaximum = true, + ExclusiveMinimum = false + } + }, + Reference = new OpenApiReference() + { + Id = "sampleSchema", + Type = ReferenceType.Schema + } + } + } + }, + Paths = new OpenApiPaths() + }); + + context.ShouldBeEquivalentTo( + new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0 }); + } + [Fact] public void ParseBasicDocumentWithMultipleServersShouldSucceed() { diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs index cefc1e486..eb51c9f3e 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System; +using System.Globalization; using System.IO; using FluentAssertions; using Microsoft.OpenApi.Any; @@ -93,7 +94,7 @@ public void WriteOpenApiDoubleAsJsonWorks(double input) public void WriteOpenApiDateTimeAsJsonWorks(string inputString) { // Arrange - var input = DateTimeOffset.Parse(inputString); + var input = DateTimeOffset.Parse(inputString, CultureInfo.InvariantCulture); var dateTimeValue = new OpenApiDateTime(input); var json = WriteAsJson(dateTimeValue);