Skip to content

Commit 841b947

Browse files
authored
Merge pull request microsoft#431 from VitaliyKurokhtin/vvk/explicit-string
Explicitly specified string must stay a string
2 parents 48fe3d5 + cefec96 commit 841b947

File tree

7 files changed

+176
-154
lines changed

7 files changed

+176
-154
lines changed

src/Microsoft.OpenApi.Readers/ParseNodes/OpenApiAnyConverter.cs

Lines changed: 113 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -6,95 +6,19 @@
66
using System.Linq;
77
using System.Text;
88
using Microsoft.OpenApi.Any;
9-
using Microsoft.OpenApi.Exceptions;
109
using Microsoft.OpenApi.Models;
1110

1211
namespace Microsoft.OpenApi.Readers.ParseNodes
1312
{
1413
internal static class OpenApiAnyConverter
1514
{
16-
/// <summary>
17-
/// Converts the <see cref="OpenApiString"/>s in the given <see cref="IOpenApiAny"/>
18-
/// into the most specific <see cref="IOpenApiPrimitive"/> type based on the value.
19-
/// </summary>
20-
public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny)
21-
{
22-
if (openApiAny is OpenApiArray openApiArray)
23-
{
24-
var newArray = new OpenApiArray();
25-
foreach (var element in openApiArray)
26-
{
27-
newArray.Add(GetSpecificOpenApiAny(element));
28-
}
29-
30-
return newArray;
31-
}
32-
33-
if (openApiAny is OpenApiObject openApiObject)
34-
{
35-
var newObject = new OpenApiObject();
36-
37-
foreach (var key in openApiObject.Keys.ToList())
38-
{
39-
newObject[key] = GetSpecificOpenApiAny(openApiObject[key]);
40-
}
41-
42-
return newObject;
43-
}
44-
45-
if ( !(openApiAny is OpenApiString))
46-
{
47-
return openApiAny;
48-
}
49-
50-
var value = ((OpenApiString)openApiAny).Value;
51-
52-
if (value == null || value == "null")
53-
{
54-
return new OpenApiNull();
55-
}
56-
57-
if (value == "true")
58-
{
59-
return new OpenApiBoolean(true);
60-
}
61-
62-
if (value == "false")
63-
{
64-
return new OpenApiBoolean(false);
65-
}
66-
67-
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
68-
{
69-
return new OpenApiInteger(intValue);
70-
}
71-
72-
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
73-
{
74-
return new OpenApiLong(longValue);
75-
}
76-
77-
if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue))
78-
{
79-
return new OpenApiDouble(doubleValue);
80-
}
81-
82-
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue))
83-
{
84-
return new OpenApiDateTime(dateTimeValue);
85-
}
86-
87-
// if we can't identify the type of value, return it as string.
88-
return new OpenApiString(value);
89-
}
90-
9115
/// <summary>
9216
/// Converts the <see cref="OpenApiString"/>s in the given <see cref="IOpenApiAny"/>
9317
/// into the appropriate <see cref="IOpenApiPrimitive"/> type based on the given <see cref="OpenApiSchema"/>.
9418
/// For those strings that the schema does not specify the type for, convert them into
9519
/// the most specific type based on the value.
9620
/// </summary>
97-
public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiSchema schema)
21+
public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiSchema schema = null)
9822
{
9923
if (openApiAny is OpenApiArray openApiArray)
10024
{
@@ -113,9 +37,9 @@ public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiS
11337

11438
foreach (var key in openApiObject.Keys.ToList())
11539
{
116-
if ( schema != null && schema.Properties != null && schema.Properties.ContainsKey(key) )
40+
if (schema?.Properties != null && schema.Properties.TryGetValue(key, out var property))
11741
{
118-
newObject[key] = GetSpecificOpenApiAny(openApiObject[key], schema.Properties[key]);
42+
newObject[key] = GetSpecificOpenApiAny(openApiObject[key], property);
11943
}
12044
else
12145
{
@@ -131,128 +55,169 @@ public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiS
13155
return openApiAny;
13256
}
13357

134-
if (schema?.Type == null)
135-
{
136-
return GetSpecificOpenApiAny(openApiAny);
137-
}
58+
var value = ((OpenApiString)openApiAny).Value;
13859

139-
var type = schema.Type;
140-
var format = schema.Format;
60+
// For explicit strings only try to guess if it's a DateTimeOffset
61+
if (((OpenApiString)openApiAny).IsExplicit())
62+
{
63+
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue))
64+
{
65+
return new OpenApiDateTime(dateTimeValue);
66+
}
14167

142-
var value = ((OpenApiString)openApiAny).Value;
68+
return openApiAny;
69+
}
14370

14471
if (value == null || value == "null")
14572
{
14673
return new OpenApiNull();
14774
}
14875

149-
if (type == "integer" && format == "int32")
76+
if (schema?.Type == null)
15077
{
151-
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
78+
if (value == "true")
15279
{
153-
return new OpenApiInteger(intValue);
80+
return new OpenApiBoolean(true);
15481
}
155-
}
15682

157-
if (type == "integer" && format == "int64")
158-
{
159-
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
83+
if (value == "false")
16084
{
161-
return new OpenApiLong(longValue);
85+
return new OpenApiBoolean(false);
16286
}
163-
}
16487

165-
if (type == "integer")
166-
{
16788
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
16889
{
16990
return new OpenApiInteger(intValue);
17091
}
171-
}
17292

173-
if (type == "number" && format == "float")
174-
{
175-
if (float.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var floatValue))
93+
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
17694
{
177-
return new OpenApiFloat(floatValue);
95+
return new OpenApiLong(longValue);
17896
}
179-
}
18097

181-
if (type == "number" && format == "double" )
182-
{
18398
if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue))
18499
{
185100
return new OpenApiDouble(doubleValue);
186101
}
187-
}
188102

189-
if (type == "number")
190-
{
191-
if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue))
103+
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue))
192104
{
193-
return new OpenApiDouble(doubleValue);
105+
return new OpenApiDateTime(dateTimeValue);
194106
}
195107
}
196-
197-
if (type == "string" && format == "byte")
108+
else
198109
{
199-
try
110+
var type = schema.Type;
111+
var format = schema.Format;
112+
113+
if (type == "integer" && format == "int32")
200114
{
201-
return new OpenApiByte(Convert.FromBase64String(value));
115+
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
116+
{
117+
return new OpenApiInteger(intValue);
118+
}
202119
}
203-
catch(FormatException)
204-
{ }
205-
}
206120

207-
// binary
208-
if (type == "string" && format == "binary")
209-
{
210-
try
121+
if (type == "integer" && format == "int64")
211122
{
212-
return new OpenApiBinary(Encoding.UTF8.GetBytes(value));
123+
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
124+
{
125+
return new OpenApiLong(longValue);
126+
}
213127
}
214-
catch(EncoderFallbackException)
215-
{ }
216-
}
217128

218-
if (type == "string" && format == "date")
219-
{
220-
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateValue))
129+
if (type == "integer")
221130
{
222-
return new OpenApiDate(dateValue.Date);
131+
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
132+
{
133+
return new OpenApiInteger(intValue);
134+
}
223135
}
224-
}
225136

226-
if (type == "string" && format == "date-time")
227-
{
228-
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue))
137+
if (type == "number" && format == "float")
229138
{
230-
return new OpenApiDateTime(dateTimeValue);
139+
if (float.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var floatValue))
140+
{
141+
return new OpenApiFloat(floatValue);
142+
}
231143
}
232-
}
233144

234-
if (type == "string" && format == "password")
235-
{
236-
return new OpenApiPassword(value);
237-
}
145+
if (type == "number" && format == "double")
146+
{
147+
if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue))
148+
{
149+
return new OpenApiDouble(doubleValue);
150+
}
151+
}
238152

239-
if (type == "string")
240-
{
241-
return new OpenApiString(value);
242-
}
153+
if (type == "number")
154+
{
155+
if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue))
156+
{
157+
return new OpenApiDouble(doubleValue);
158+
}
159+
}
243160

244-
if (type == "boolean")
245-
{
246-
if (bool.TryParse(value, out var booleanValue))
161+
if (type == "string" && format == "byte")
162+
{
163+
try
164+
{
165+
return new OpenApiByte(Convert.FromBase64String(value));
166+
}
167+
catch (FormatException)
168+
{ }
169+
}
170+
171+
// binary
172+
if (type == "string" && format == "binary")
173+
{
174+
try
175+
{
176+
return new OpenApiBinary(Encoding.UTF8.GetBytes(value));
177+
}
178+
catch (EncoderFallbackException)
179+
{ }
180+
}
181+
182+
if (type == "string" && format == "date")
247183
{
248-
return new OpenApiBoolean(booleanValue);
184+
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateValue))
185+
{
186+
return new OpenApiDate(dateValue.Date);
187+
}
188+
}
189+
190+
if (type == "string" && format == "date-time")
191+
{
192+
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue))
193+
{
194+
return new OpenApiDateTime(dateTimeValue);
195+
}
196+
}
197+
198+
if (type == "string" && format == "password")
199+
{
200+
return new OpenApiPassword(value);
201+
}
202+
203+
if (type == "string")
204+
{
205+
return new OpenApiString(value);
206+
}
207+
208+
if (type == "boolean")
209+
{
210+
if (bool.TryParse(value, out var booleanValue))
211+
{
212+
return new OpenApiBoolean(booleanValue);
213+
}
249214
}
250215
}
251216

252217
// If data conflicts with the given type, return a string.
253218
// This converter is used in the parser, so it does not perform any validations,
254219
// but the validator can be used to validate whether the data and given type conflicts.
255-
return new OpenApiString(value);
220+
return openApiAny;
256221
}
257222
}
258223
}

src/Microsoft.OpenApi.Readers/ParseNodes/ValueNode.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

4-
using System;
5-
using System.Globalization;
64
using Microsoft.OpenApi.Any;
75
using Microsoft.OpenApi.Readers.Exceptions;
6+
using SharpYaml;
87
using SharpYaml.Serialization;
98

109
namespace Microsoft.OpenApi.Readers.ParseNodes
@@ -35,7 +34,7 @@ public override string GetScalarValue()
3534
public override IOpenApiAny CreateAny()
3635
{
3736
var value = GetScalarValue();
38-
return new OpenApiString(value);
37+
return new OpenApiString(value, this._node.Style == ScalarStyle.SingleQuoted || this._node.Style == ScalarStyle.DoubleQuoted || this._node.Style == ScalarStyle.Literal || this._node.Style == ScalarStyle.Folded);
3938
}
4039
}
4140
}

0 commit comments

Comments
 (0)