Skip to content

Commit a5bc63d

Browse files
.Net: DefaultValue for OpenAPI payload properties (microsoft#4612)
### Motivation and Context Currently, the default values of parameters in OpenAPI plugins have the 'string?' type, even though their original type specified in the OpenAPI document schema might be different. A dedicated type conversion functionality exists to convert from the original type to a string. However, both the string type for the parameters/arguments and the type conversion logic are no longer necessary, as SK has moved away from string-type arguments and recently added support for .NET primitive types and complex types. Furthermore, the `RestApiOperationPayloadProperty` class does not have a property for default values, so a default value specified in an OpenAPI document is lost and cannot be accessed from the code. ### Description 1. The type of the `DefaultValue` property in the `RestApiOperationParameter` class has been changed from string? to object? to better represent the actual type of the parameter's default value and eliminate unnecessary conversion to string. 2. A new `DefaultValue` property has been added to the `RestApiOperationPayloadProperty`, making it consistent with the RestApiOperationParameter class.
1 parent b2bbd41 commit a5bc63d

11 files changed

+533
-42
lines changed

dotnet/src/Functions/Functions.OpenApi/Extensions/RestApiOperationExtensions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ private static List<RestApiOperationParameter> GetParametersFromPayloadMetadata(
179179
expand: false,
180180
RestApiOperationParameterLocation.Body,
181181
RestApiOperationParameterStyle.Simple,
182+
defaultValue: property.DefaultValue,
182183
description: property.Description,
183184
schema: property.Schema));
184185
}

dotnet/src/Functions/Functions.OpenApi/Model/RestApiOperationParameter.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public sealed class RestApiOperationParameter
5050
/// <summary>
5151
/// The default value.
5252
/// </summary>
53-
public string? DefaultValue { get; }
53+
public object? DefaultValue { get; }
5454

5555
/// <summary>
5656
/// Specifies whether arrays and objects should generate separate parameters for each array item or object property.
@@ -83,7 +83,7 @@ public RestApiOperationParameter(
8383
RestApiOperationParameterLocation location,
8484
RestApiOperationParameterStyle? style = null,
8585
string? arrayItemType = null,
86-
string? defaultValue = null,
86+
object? defaultValue = null,
8787
string? description = null,
8888
KernelJsonSchema? schema = null)
8989
{

dotnet/src/Functions/Functions.OpenApi/Model/RestApiOperationPayloadProperty.cs

+15-6
Original file line numberDiff line numberDiff line change
@@ -39,28 +39,37 @@ public sealed class RestApiOperationPayloadProperty
3939
/// </summary>
4040
public KernelJsonSchema? Schema { get; }
4141

42+
/// <summary>
43+
/// The default value.
44+
/// </summary>
45+
public object? DefaultValue { get; }
46+
4247
/// <summary>
4348
/// Creates an instance of a <see cref="RestApiOperationPayloadProperty"/> class.
4449
/// </summary>
45-
/// <param name="name">Property name.</param>
46-
/// <param name="type">Property type.</param>
47-
/// <param name="isRequired">Flag specifying if the property is required or not.</param>
48-
/// <param name="properties">Properties.</param>
49-
/// <param name="description">Property description.</param>
50+
/// <param name="name">The name of the property.</param>
51+
/// <param name="type">The type of the property.</param>
52+
/// <param name="isRequired">A flag specifying if the property is required or not.</param>
53+
/// <param name="properties">A list of properties for the payload property.</param>
54+
/// <param name="description">A description of the property.</param>
5055
/// <param name="schema">The schema of the payload property.</param>
56+
/// <param name="defaultValue">The default value of the property.</param>
57+
/// <returns>Returns a new instance of the <see cref="RestApiOperationPayloadProperty"/> class.</returns>
5158
public RestApiOperationPayloadProperty(
5259
string name,
5360
string type,
5461
bool isRequired,
5562
IList<RestApiOperationPayloadProperty> properties,
5663
string? description = null,
57-
KernelJsonSchema? schema = null)
64+
KernelJsonSchema? schema = null,
65+
object? defaultValue = null)
5866
{
5967
this.Name = name;
6068
this.Type = type;
6169
this.IsRequired = isRequired;
6270
this.Description = description;
6371
this.Properties = properties;
6472
this.Schema = schema;
73+
this.DefaultValue = defaultValue;
6574
}
6675
}

dotnet/src/Functions/Functions.OpenApi/OpenApi/OpenApiDocumentParser.cs

+14-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
using System;
44
using System.Collections.Generic;
5-
using System.Globalization;
65
using System.IO;
76
using System.Linq;
87
using System.Net.Http;
@@ -313,7 +312,8 @@ private static List<RestApiOperationPayloadProperty> GetPayloadProperties(string
313312
requiredProperties.Contains(propertyName),
314313
GetPayloadProperties(operationId, propertySchema, requiredProperties, level + 1),
315314
propertySchema.Description,
316-
propertySchema.ToJsonSchema());
315+
propertySchema.ToJsonSchema(),
316+
GetParameterValue(propertySchema.Default));
317317

318318
result.Add(property);
319319
}
@@ -326,7 +326,7 @@ private static List<RestApiOperationPayloadProperty> GetPayloadProperties(string
326326
/// </summary>
327327
/// <param name="valueMetadata">The value metadata.</param>
328328
/// <returns>The parameter value.</returns>
329-
private static string? GetParameterValue(IOpenApiAny valueMetadata)
329+
private static object? GetParameterValue(IOpenApiAny valueMetadata)
330330
{
331331
if (valueMetadata is not IOpenApiPrimitive value)
332332
{
@@ -335,17 +335,17 @@ private static List<RestApiOperationPayloadProperty> GetPayloadProperties(string
335335

336336
return value.PrimitiveType switch
337337
{
338-
PrimitiveType.Integer => ((OpenApiInteger)value).Value.ToString(CultureInfo.InvariantCulture),
339-
PrimitiveType.Long => ((OpenApiLong)value).Value.ToString(CultureInfo.InvariantCulture),
340-
PrimitiveType.Float => ((OpenApiFloat)value).Value.ToString(CultureInfo.InvariantCulture),
341-
PrimitiveType.Double => ((OpenApiDouble)value).Value.ToString(CultureInfo.InvariantCulture),
342-
PrimitiveType.String => ((OpenApiString)value).Value.ToString(CultureInfo.InvariantCulture),
343-
PrimitiveType.Byte => Convert.ToBase64String(((OpenApiByte)value).Value),
344-
PrimitiveType.Binary => Encoding.UTF8.GetString(((OpenApiBinary)value).Value),
345-
PrimitiveType.Boolean => ((OpenApiBoolean)value).Value.ToString(CultureInfo.InvariantCulture),
346-
PrimitiveType.Date => ((OpenApiDate)value).Value.ToString("o").Substring(0, 10),
347-
PrimitiveType.DateTime => ((OpenApiDateTime)value).Value.ToString(CultureInfo.InvariantCulture),
348-
PrimitiveType.Password => ((OpenApiPassword)value).Value.ToString(CultureInfo.InvariantCulture),
338+
PrimitiveType.Integer => ((OpenApiInteger)value).Value,
339+
PrimitiveType.Long => ((OpenApiLong)value).Value,
340+
PrimitiveType.Float => ((OpenApiFloat)value).Value,
341+
PrimitiveType.Double => ((OpenApiDouble)value).Value,
342+
PrimitiveType.String => ((OpenApiString)value).Value,
343+
PrimitiveType.Byte => ((OpenApiByte)value).Value,
344+
PrimitiveType.Binary => ((OpenApiBinary)value).Value,
345+
PrimitiveType.Boolean => ((OpenApiBoolean)value).Value,
346+
PrimitiveType.Date => ((OpenApiDate)value).Value,
347+
PrimitiveType.DateTime => ((OpenApiDateTime)value).Value,
348+
PrimitiveType.Password => ((OpenApiPassword)value).Value,
349349
_ => throw new KernelException($"The value type - {value.PrimitiveType} is not supported."),
350350
};
351351
}

dotnet/src/Functions/Functions.UnitTests/OpenApi/Extensions/RestApiOperationExtensionsTests.cs

+47-12
Original file line numberDiff line numberDiff line change
@@ -280,18 +280,53 @@ private static RestApiOperation CreateTestOperation(string method, RestApiOperat
280280

281281
private static RestApiOperationPayload CreateTestJsonPayload()
282282
{
283-
var name = new RestApiOperationPayloadProperty("name", "string", true, new List<RestApiOperationPayloadProperty>(), "The name.");
284-
285-
var leader = new RestApiOperationPayloadProperty("leader", "string", true, new List<RestApiOperationPayloadProperty>(), "The leader.");
286-
287-
var landmarks = new RestApiOperationPayloadProperty("landmarks", "array", false, new List<RestApiOperationPayloadProperty>(), "The landmarks.");
288-
var location = new RestApiOperationPayloadProperty("location", "object", true, new[] { landmarks }, "The location.");
289-
290-
var rulingCouncil = new RestApiOperationPayloadProperty("rulingCouncil", "object", true, new[] { leader }, "The ruling council.");
291-
292-
var population = new RestApiOperationPayloadProperty("population", "integer", true, new List<RestApiOperationPayloadProperty>(), "The population.");
293-
294-
var hasMagicWards = new RestApiOperationPayloadProperty("hasMagicWards", "boolean", false, new List<RestApiOperationPayloadProperty>());
283+
var name = new RestApiOperationPayloadProperty(
284+
name: "name",
285+
type: "string",
286+
isRequired: true,
287+
properties: new List<RestApiOperationPayloadProperty>(),
288+
description: "The name.");
289+
290+
var leader = new RestApiOperationPayloadProperty(
291+
name: "leader",
292+
type: "string",
293+
isRequired: true,
294+
properties: new List<RestApiOperationPayloadProperty>(),
295+
description: "The leader.");
296+
297+
var landmarks = new RestApiOperationPayloadProperty(
298+
name: "landmarks",
299+
type: "array",
300+
isRequired: false,
301+
properties: new List<RestApiOperationPayloadProperty>(),
302+
description: "The landmarks.");
303+
304+
var location = new RestApiOperationPayloadProperty(
305+
name: "location",
306+
type: "object",
307+
isRequired: true,
308+
properties: new[] { landmarks },
309+
description: "The location.");
310+
311+
var rulingCouncil = new RestApiOperationPayloadProperty(
312+
name: "rulingCouncil",
313+
type: "object",
314+
isRequired: true,
315+
properties: new[] { leader },
316+
description: "The ruling council.");
317+
318+
var population = new RestApiOperationPayloadProperty(
319+
name: "population",
320+
type: "integer",
321+
isRequired: true,
322+
properties: new List<RestApiOperationPayloadProperty>(),
323+
description: "The population.");
324+
325+
var hasMagicWards = new RestApiOperationPayloadProperty(
326+
name: "hasMagicWards",
327+
type: "boolean",
328+
isRequired: false,
329+
properties: new List<RestApiOperationPayloadProperty>());
295330

296331
return new RestApiOperationPayload("application/json", new[] { name, location, rulingCouncil, population, hasMagicWards });
297332
}

dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserV20Tests.cs

+61-2
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ public async Task ItCanExtractSimpleTypeHeaderParameterMetadataSuccessfullyAsync
175175
var apiVersion = GetParameterMetadata(operations, "SetSecret", RestApiOperationParameterLocation.Header, "X-API-Version");
176176

177177
Assert.Equal("integer", apiVersion.Type);
178-
Assert.Equal("10", apiVersion.DefaultValue);
178+
Assert.Equal(10, apiVersion.DefaultValue);
179179
Assert.Equal("Requested API version.", apiVersion.Description);
180180
Assert.True(apiVersion.IsRequired);
181181
}
@@ -225,7 +225,7 @@ public async Task ItCanExtractAllPathsAsOperationsAsync()
225225
var operations = await this._sut.ParseAsync(this._openApiDocument);
226226

227227
// Assert
228-
Assert.Equal(3, operations.Count);
228+
Assert.Equal(4, operations.Count);
229229
}
230230

231231
[Fact]
@@ -293,6 +293,65 @@ public async Task ItCanParseResponsesSuccessfullyAsync()
293293
JsonSerializer.Serialize(response.Schema));
294294
}
295295

296+
[Fact]
297+
public async Task ItCanWorkWithDefaultParametersOfVariousTypesAsync()
298+
{
299+
//Act
300+
var operations = await this._sut.ParseAsync(this._openApiDocument);
301+
302+
//Assert
303+
Assert.NotNull(operations);
304+
Assert.True(operations.Any());
305+
306+
var operation = operations.Single(o => o.Id == "TestDefaultValues");
307+
Assert.NotNull(operation);
308+
309+
var parameters = operation.GetParameters();
310+
Assert.Equal(11, parameters.Count);
311+
312+
var stringParameter = parameters.Single(p => p.Name == "string-parameter");
313+
Assert.Equal("string-value", stringParameter.DefaultValue);
314+
315+
var booleanParameter = parameters.Single(p => p.Name == "boolean-parameter");
316+
Assert.True(booleanParameter.DefaultValue is bool value);
317+
318+
var integerParameter = parameters.Single(p => p.Name == "integer-parameter");
319+
Assert.True(integerParameter.DefaultValue is int);
320+
Assert.Equal(281, integerParameter.DefaultValue);
321+
322+
var longParameter = parameters.Single(p => p.Name == "long-parameter");
323+
Assert.True(longParameter.DefaultValue is long);
324+
Assert.Equal((long)-2814, longParameter.DefaultValue);
325+
326+
var floatParameter = parameters.Single(p => p.Name == "float-parameter");
327+
Assert.True(floatParameter.DefaultValue is float);
328+
Assert.Equal((float)12.01, floatParameter.DefaultValue);
329+
330+
var doubleParameter = parameters.Single(p => p.Name == "double-parameter");
331+
Assert.True(doubleParameter.DefaultValue is double);
332+
Assert.Equal((double)-12.01, doubleParameter.DefaultValue);
333+
334+
var encodedCharactersParameter = parameters.Single(p => p.Name == "encoded-characters-parameter");
335+
Assert.True(encodedCharactersParameter.DefaultValue is byte[]);
336+
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, encodedCharactersParameter.DefaultValue);
337+
338+
var binaryDataParameter = parameters.Single(p => p.Name == "binary-data-parameter");
339+
Assert.True(binaryDataParameter.DefaultValue is byte[]);
340+
Assert.Equal(new byte[] { 50, 51, 52, 53, 54 }, binaryDataParameter.DefaultValue);
341+
342+
var dateParameter = parameters.Single(p => p.Name == "date-parameter");
343+
Assert.True(dateParameter.DefaultValue is DateTime);
344+
Assert.Equal(new DateTime(2017, 07, 21), dateParameter.DefaultValue);
345+
346+
var dateTimeParameter = parameters.Single(p => p.Name == "date-time-parameter");
347+
Assert.True(dateTimeParameter.DefaultValue is DateTimeOffset);
348+
Assert.Equal(new DateTimeOffset(2017, 07, 21, 17, 32, 28, TimeSpan.Zero), dateTimeParameter.DefaultValue);
349+
350+
var passwordParameter = parameters.Single(p => p.Name == "password-parameter");
351+
Assert.True(passwordParameter.DefaultValue is string);
352+
Assert.Equal("password-value", passwordParameter.DefaultValue);
353+
}
354+
296355
private static RestApiOperationParameter GetParameterMetadata(IList<RestApiOperation> operations, string operationId,
297356
RestApiOperationParameterLocation location, string name)
298357
{

dotnet/src/Functions/Functions.UnitTests/OpenApi/OpenApiDocumentParserV30Tests.cs

+61-2
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public async Task ItCanExtractSimpleTypeHeaderParameterMetadataSuccessfullyAsync
161161
var apiVersion = GetParameterMetadata(operations, "SetSecret", RestApiOperationParameterLocation.Header, "X-API-Version");
162162

163163
Assert.Equal("integer", apiVersion.Type);
164-
Assert.Equal("10", apiVersion.DefaultValue);
164+
Assert.Equal(10, apiVersion.DefaultValue);
165165
Assert.Equal("Requested API version.", apiVersion.Description);
166166
Assert.True(apiVersion.IsRequired);
167167
}
@@ -226,7 +226,7 @@ public async Task ItCanExtractAllPathsAsOperationsAsync()
226226
var operations = await this._sut.ParseAsync(this._openApiDocument);
227227

228228
// Assert
229-
Assert.Equal(3, operations.Count);
229+
Assert.Equal(4, operations.Count);
230230
}
231231

232232
[Fact]
@@ -366,6 +366,65 @@ public async Task ItCanParseResponsesSuccessfullyAsync()
366366
JsonSerializer.Serialize(response.Schema));
367367
}
368368

369+
[Fact]
370+
public async Task ItCanWorkWithDefaultParametersOfVariousTypesAsync()
371+
{
372+
//Act
373+
var operations = await this._sut.ParseAsync(this._openApiDocument);
374+
375+
//Assert
376+
Assert.NotNull(operations);
377+
Assert.True(operations.Any());
378+
379+
var operation = operations.Single(o => o.Id == "TestDefaultValues");
380+
Assert.NotNull(operation);
381+
382+
var parameters = operation.GetParameters();
383+
Assert.Equal(11, parameters.Count);
384+
385+
var stringParameter = parameters.Single(p => p.Name == "string-parameter");
386+
Assert.Equal("string-value", stringParameter.DefaultValue);
387+
388+
var booleanParameter = parameters.Single(p => p.Name == "boolean-parameter");
389+
Assert.True(booleanParameter.DefaultValue is bool value);
390+
391+
var integerParameter = parameters.Single(p => p.Name == "integer-parameter");
392+
Assert.True(integerParameter.DefaultValue is int);
393+
Assert.Equal(281, integerParameter.DefaultValue);
394+
395+
var longParameter = parameters.Single(p => p.Name == "long-parameter");
396+
Assert.True(longParameter.DefaultValue is long);
397+
Assert.Equal((long)-2814, longParameter.DefaultValue);
398+
399+
var floatParameter = parameters.Single(p => p.Name == "float-parameter");
400+
Assert.True(floatParameter.DefaultValue is float);
401+
Assert.Equal((float)12.01, floatParameter.DefaultValue);
402+
403+
var doubleParameter = parameters.Single(p => p.Name == "double-parameter");
404+
Assert.True(doubleParameter.DefaultValue is double);
405+
Assert.Equal((double)-12.01, doubleParameter.DefaultValue);
406+
407+
var encodedCharactersParameter = parameters.Single(p => p.Name == "encoded-characters-parameter");
408+
Assert.True(encodedCharactersParameter.DefaultValue is byte[]);
409+
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, encodedCharactersParameter.DefaultValue);
410+
411+
var binaryDataParameter = parameters.Single(p => p.Name == "binary-data-parameter");
412+
Assert.True(binaryDataParameter.DefaultValue is byte[]);
413+
Assert.Equal(new byte[] { 50, 51, 52, 53, 54 }, binaryDataParameter.DefaultValue);
414+
415+
var dateParameter = parameters.Single(p => p.Name == "date-parameter");
416+
Assert.True(dateParameter.DefaultValue is DateTime);
417+
Assert.Equal(new DateTime(2017, 07, 21), dateParameter.DefaultValue);
418+
419+
var dateTimeParameter = parameters.Single(p => p.Name == "date-time-parameter");
420+
Assert.True(dateTimeParameter.DefaultValue is DateTimeOffset);
421+
Assert.Equal(new DateTimeOffset(2017, 07, 21, 17, 32, 28, TimeSpan.Zero), dateTimeParameter.DefaultValue);
422+
423+
var passwordParameter = parameters.Single(p => p.Name == "password-parameter");
424+
Assert.True(passwordParameter.DefaultValue is string);
425+
Assert.Equal("password-value", passwordParameter.DefaultValue);
426+
}
427+
369428
private static MemoryStream ModifyOpenApiDocument(Stream openApiDocument, Action<JsonObject> transformer)
370429
{
371430
var json = JsonSerializer.Deserialize<JsonObject>(openApiDocument);

0 commit comments

Comments
 (0)