Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicitly specified string must stay a string #431

Merged
merged 3 commits into from
Jan 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 113 additions & 148 deletions src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/// <summary>
/// Converts the <see cref="OpenApiString"/>s in the given <see cref="IOpenApiAny"/>
/// into the most specific <see cref="IOpenApiPrimitive"/> type based on the value.
/// </summary>
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);
}

/// <summary>
/// Converts the <see cref="OpenApiString"/>s in the given <see cref="IOpenApiAny"/>
/// into the appropriate <see cref="IOpenApiPrimitive"/> type based on the given <see cref="OpenApiSchema"/>.
/// For those strings that the schema does not specify the type for, convert them into
/// the most specific type based on the value.
/// </summary>
public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiSchema schema)
public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiSchema schema = null)
{
if (openApiAny is OpenApiArray openApiArray)
{
Expand All @@ -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
{
Expand All @@ -131,128 +55,169 @@ public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiS
return openApiAny;
}

if (schema?.Type == null)
{
return GetSpecificOpenApiAny(openApiAny);
}
var value = ((OpenApiString)openApiAny).Value;

var type = schema.Type;
var format = schema.Format;
// For explicit strings only try to guess if it's a DateTimeOffset
if (((OpenApiString)openApiAny).IsExplicit())
{
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue))
{
return new OpenApiDateTime(dateTimeValue);
}

var value = ((OpenApiString)openApiAny).Value;
return openApiAny;
}

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")
{
try
{
return new OpenApiBinary(Encoding.UTF8.GetBytes(value));
}
catch (EncoderFallbackException)
{ }
}

if (type == "string" && format == "date")
{
return new OpenApiBoolean(booleanValue);
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;
}
}
}
5 changes: 2 additions & 3 deletions src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -35,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);
}
}
}
Loading