Skip to content

Commit 2c7663a

Browse files
Explicitly specified string must stay a string
1 parent 578e587 commit 2c7663a

File tree

7 files changed

+164
-157
lines changed

7 files changed

+164
-157
lines changed

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

Lines changed: 104 additions & 151 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
{
@@ -126,133 +50,162 @@ public static IOpenApiAny GetSpecificOpenApiAny(IOpenApiAny openApiAny, OpenApiS
12650
return newObject;
12751
}
12852

129-
if (!(openApiAny is OpenApiString))
53+
if (!(openApiAny is OpenApiString) || ((OpenApiString)openApiAny).IsExplicit())
13054
{
13155
return openApiAny;
13256
}
13357

134-
if (schema?.Type == null)
135-
{
136-
return GetSpecificOpenApiAny(openApiAny);
137-
}
138-
139-
var type = schema.Type;
140-
var format = schema.Format;
141-
14258
var value = ((OpenApiString)openApiAny).Value;
143-
14459
if (value == null || value == "null")
14560
{
14661
return new OpenApiNull();
14762
}
14863

149-
if (type == "integer" && format == "int32")
64+
if (schema?.Type == null)
15065
{
151-
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
66+
if (value == "true")
15267
{
153-
return new OpenApiInteger(intValue);
68+
return new OpenApiBoolean(true);
15469
}
155-
}
15670

157-
if (type == "integer" && format == "int64")
158-
{
159-
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
71+
if (value == "false")
16072
{
161-
return new OpenApiLong(longValue);
73+
return new OpenApiBoolean(false);
16274
}
163-
}
16475

165-
if (type == "integer")
166-
{
16776
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
16877
{
16978
return new OpenApiInteger(intValue);
17079
}
171-
}
17280

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

181-
if (type == "number" && format == "double" )
182-
{
18386
if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue))
18487
{
18588
return new OpenApiDouble(doubleValue);
18689
}
187-
}
18890

189-
if (type == "number")
190-
{
191-
if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue))
91+
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue))
19292
{
193-
return new OpenApiDouble(doubleValue);
93+
return new OpenApiDateTime(dateTimeValue);
19494
}
19595
}
196-
197-
if (type == "string" && format == "byte")
96+
else
19897
{
199-
try
98+
var type = schema.Type;
99+
var format = schema.Format;
100+
101+
if (type == "integer" && format == "int32")
200102
{
201-
return new OpenApiByte(Convert.FromBase64String(value));
103+
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
104+
{
105+
return new OpenApiInteger(intValue);
106+
}
202107
}
203-
catch(FormatException)
204-
{ }
205-
}
206108

207-
// binary
208-
if (type == "string" && format == "binary")
209-
{
210-
try
109+
if (type == "integer" && format == "int64")
211110
{
212-
return new OpenApiBinary(Encoding.UTF8.GetBytes(value));
111+
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
112+
{
113+
return new OpenApiLong(longValue);
114+
}
213115
}
214-
catch(EncoderFallbackException)
215-
{ }
216-
}
217116

218-
if (type == "string" && format == "date")
219-
{
220-
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateValue))
117+
if (type == "integer")
221118
{
222-
return new OpenApiDate(dateValue.Date);
119+
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
120+
{
121+
return new OpenApiInteger(intValue);
122+
}
223123
}
224-
}
225124

226-
if (type == "string" && format == "date-time")
227-
{
228-
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue))
125+
if (type == "number" && format == "float")
229126
{
230-
return new OpenApiDateTime(dateTimeValue);
127+
if (float.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var floatValue))
128+
{
129+
return new OpenApiFloat(floatValue);
130+
}
231131
}
232-
}
233132

234-
if (type == "string" && format == "password")
235-
{
236-
return new OpenApiPassword(value);
237-
}
133+
if (type == "number" && format == "double")
134+
{
135+
if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue))
136+
{
137+
return new OpenApiDouble(doubleValue);
138+
}
139+
}
238140

239-
if (type == "string")
240-
{
241-
return new OpenApiString(value);
242-
}
141+
if (type == "number")
142+
{
143+
if (double.TryParse(value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var doubleValue))
144+
{
145+
return new OpenApiDouble(doubleValue);
146+
}
147+
}
243148

244-
if (type == "boolean")
245-
{
246-
if (bool.TryParse(value, out var booleanValue))
149+
if (type == "string" && format == "byte")
150+
{
151+
try
152+
{
153+
return new OpenApiByte(Convert.FromBase64String(value));
154+
}
155+
catch (FormatException)
156+
{ }
157+
}
158+
159+
// binary
160+
if (type == "string" && format == "binary")
247161
{
248-
return new OpenApiBoolean(booleanValue);
162+
try
163+
{
164+
return new OpenApiBinary(Encoding.UTF8.GetBytes(value));
165+
}
166+
catch (EncoderFallbackException)
167+
{ }
168+
}
169+
170+
if (type == "string" && format == "date")
171+
{
172+
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateValue))
173+
{
174+
return new OpenApiDate(dateValue.Date);
175+
}
176+
}
177+
178+
if (type == "string" && format == "date-time")
179+
{
180+
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateTimeValue))
181+
{
182+
return new OpenApiDateTime(dateTimeValue);
183+
}
184+
}
185+
186+
if (type == "string" && format == "password")
187+
{
188+
return new OpenApiPassword(value);
189+
}
190+
191+
if (type == "string")
192+
{
193+
return new OpenApiString(value);
194+
}
195+
196+
if (type == "boolean")
197+
{
198+
if (bool.TryParse(value, out var booleanValue))
199+
{
200+
return new OpenApiBoolean(booleanValue);
201+
}
249202
}
250203
}
251204

252205
// If data conflicts with the given type, return a string.
253206
// This converter is used in the parser, so it does not perform any validations,
254207
// but the validator can be used to validate whether the data and given type conflicts.
255-
return new OpenApiString(value);
208+
return openApiAny;
256209
}
257210
}
258211
}

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
@@ -36,7 +35,7 @@ public override string GetScalarValue()
3635
public override IOpenApiAny CreateAny()
3736
{
3837
var value = GetScalarValue();
39-
return new OpenApiString(value);
38+
return new OpenApiString(value, this._node.Style == ScalarStyle.SingleQuoted || this._node.Style == ScalarStyle.DoubleQuoted || this._node.Style == ScalarStyle.Literal || this._node.Style == ScalarStyle.Folded);
4039
}
4140
}
4241
}

src/Microsoft.OpenApi/Any/OpenApiString.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,29 @@ namespace Microsoft.OpenApi.Any
88
/// </summary>
99
public class OpenApiString : OpenApiPrimitive<string>
1010
{
11+
private bool isExplicit;
12+
1113
/// <summary>
1214
/// Initializes the <see cref="OpenApiString"/> class.
1315
/// </summary>
1416
/// <param name="value"></param>
15-
public OpenApiString(string value)
17+
public OpenApiString(string value, bool isExplicit = false)
1618
: base(value)
1719
{
20+
this.isExplicit = isExplicit;
1821
}
1922

2023
/// <summary>
2124
/// The primitive class this object represents.
2225
/// </summary>
2326
public override PrimitiveType PrimitiveType { get; } = PrimitiveType.String;
27+
28+
/// <summary>
29+
/// True if string was specified explicitly by the means of double quotes, single quotes, or literal or folded style.
30+
/// </summary>
31+
public bool IsExplicit()
32+
{
33+
return this.isExplicit;
34+
}
2435
}
2536
}

0 commit comments

Comments
 (0)