diff --git a/src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs
index 50ec431cc..5a6672eab 100644
--- a/src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs
+++ b/src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs
@@ -6,95 +6,19 @@
using System.Linq;
using System.Text;
using Microsoft.OpenApi.Any;
-using Microsoft.OpenApi.Exceptions;
using Microsoft.OpenApi.Models;
namespace Microsoft.OpenApi.Readers.ParseNodes
{
internal static class OpenApiAnyConverter
{
- ///
- /// Converts the s in the given
- /// into the most specific type based on the value.
- ///
- public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny)
- {
- if (openApiAny is OpenApiArray openApiArray)
- {
- var newArray = new OpenApiArray();
- foreach (var element in openApiArray)
- {
- newArray.Add(GetSpecificOpenApiAny(element));
- }
-
- return newArray;
- }
-
- if (openApiAny is OpenApiObject openApiObject)
- {
- var newObject = new OpenApiObject();
-
- foreach (var key in openApiObject.Keys.ToList())
- {
- newObject[key] = GetSpecificOpenApiAny(openApiObject[key]);
- }
-
- return newObject;
- }
-
- if ( !(openApiAny is OpenApiString))
- {
- return openApiAny;
- }
-
- var value = ((OpenApiString)openApiAny).Value;
-
- 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))
- {
- 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)
+ public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiSchema schema = null)
{
if (openApiAny is OpenApiArray openApiArray)
{
@@ -113,9 +37,9 @@ public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiS
foreach (var key in openApiObject.Keys.ToList())
{
- if ( schema != null && schema.Properties != null && schema.Properties.ContainsKey(key) )
+ if (schema?.Properties != null && schema.Properties.TryGetValue(key, out var property))
{
- newObject[key] = GetSpecificOpenApiAny(openApiObject[key], schema.Properties[key]);
+ newObject[key] = GetSpecificOpenApiAny(openApiObject[key], property);
}
else
{
@@ -126,133 +50,162 @@ public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiS
return newObject;
}
- if (!(openApiAny is OpenApiString))
+ if (!(openApiAny is OpenApiString) || ((OpenApiString)openApiAny).IsExplicit())
{
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
+ var type = schema.Type;
+ var format = schema.Format;
+
+ 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 new OpenApiString(value);
+ }
+
+ 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/ValueNode.cs b/src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs
index b7669b538..324510f87 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
@@ -36,7 +35,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/Any/OpenApiString.cs b/src/Microsoft.OpenApi/Any/OpenApiString.cs
index ebd31449e..2ead9b6c5 100644
--- a/src/Microsoft.OpenApi/Any/OpenApiString.cs
+++ b/src/Microsoft.OpenApi/Any/OpenApiString.cs
@@ -8,18 +8,29 @@ namespace Microsoft.OpenApi.Any
///
public class OpenApiString : OpenApiPrimitive
{
+ private bool isExplicit;
+
///
/// Initializes the class.
///
///
- public OpenApiString(string value)
+ 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/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj
index e24afc871..ba3df3cc7 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
@@ -143,6 +146,7 @@
Never
+
Never
diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs
index 20e3f33eb..e38893884 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs
@@ -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..089ae57a2 100644
--- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs
+++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs
@@ -71,8 +71,8 @@ public void ParseAdvancedInfoShouldSucceed()
},
["x-list"] = new OpenApiArray
{
- new OpenApiInteger(1),
- new OpenApiInteger(2)
+ new OpenApiString("1"),
+ new OpenApiString("2")
}
}
});
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..bb2baa79e
--- /dev/null
+++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/Samples/OpenApiExample/explicitString.yaml
@@ -0,0 +1,33 @@
+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'
+ required:
+ - origin
+ example:
+ sub:
+ test-property: "12345"
+ test-sub-schema:
+ type: object
+ properties:
+ test-property:
+ type: string
+ example: "12345"
\ No newline at end of file