Skip to content

Commit

Permalink
Merge pull request #4087 from microsoft/bugfix/required-query-parameters
Browse files Browse the repository at this point in the history
- projects required query parameters as required in the template
  • Loading branch information
baywet authored Jan 31, 2024
2 parents 801f08b + d6291f9 commit 4ed90ca
Show file tree
Hide file tree
Showing 3 changed files with 284 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed serialization of scalar members in union types for Python. [#2828](https://github.com/microsoft/kiota/issues/2828)
- Fixed a bug where scalar error mappings would be generated even though it's not supported by the http request adapter. [#4018](https://github.com/microsoft/kiota/issues/4018)
- Switched to proxy generation for TypeScript, leading to about ~44% bundle sizes reduction. [#3642](https://github.com/microsoft/kiota/issues/3642)
- Required query parameters are now projected as `{baseurl+}foo/bar?required={required}` instead of `{baseurl+}foo/bar{?required}` so they are automatically populated if no value is provided. [#3989](https://github.com/microsoft/kiota/issues/3989)
- Fixed a bug where TypeScript models factory methods would be missing return types.
- Fixed a bug where generated paths would possibly get too long. [#3854](https://github.com/microsoft/kiota/issues/3854)
- The vscode extension now also displays the children nodes when filtering. [#3998](https://github.com/microsoft/kiota/issues/3998)
Expand Down
19 changes: 13 additions & 6 deletions src/Kiota.Builder/Extensions/OpenApiUrlTreeNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,16 +194,23 @@ public static string GetUrlTemplate(this OpenApiUrlTreeNode currentNode)
pathItem.Operations
.SelectMany(static x => x.Value.Parameters)
.Where(static x => x.In == ParameterLocation.Query))
.DistinctBy(static x => x.Name)
.DistinctBy(static x => x.Name, StringComparer.Ordinal)
.OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase)
.ToArray();
if (parameters.Length != 0)
queryStringParameters = "{?" +
parameters.Select(static x =>
{
var requiredParameters = string.Join("&", parameters.Where(static x => x.Required)
.Select(static x =>
$"{x.Name}={{{x.Name.SanitizeParameterNameForUrlTemplate()}}}"));
var optionalParameters = string.Join(",", parameters.Where(static x => !x.Required)
.Select(static x =>
x.Name.SanitizeParameterNameForUrlTemplate() +
(x.Explode ?
"*" : string.Empty))
.Aggregate(static (x, y) => $"{x},{y}") +
'}';
"*" : string.Empty)));
var hasRequiredParameters = !string.IsNullOrEmpty(requiredParameters);
var hasOptionalParameters = !string.IsNullOrEmpty(optionalParameters);
queryStringParameters = $"{(hasRequiredParameters ? "?" : string.Empty)}{requiredParameters}{(hasOptionalParameters ? "{" : string.Empty)}{(hasOptionalParameters && hasRequiredParameters ? "&" : string.Empty)}{(hasOptionalParameters && !hasRequiredParameters ? "?" : string.Empty)}{optionalParameters}{(hasOptionalParameters ? "}" : string.Empty)}";
}
}
var pathReservedPathParametersIds = currentNode.PathItems.TryGetValue(Constants.DefaultOpenApiLabel, out var pItem) ?
pItem.Parameters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,13 @@ public void GetUrlTemplateSelectsDistinctQueryParameters()
{
var doc = new OpenApiDocument
{
Paths = new(),
Paths = [],
};
doc.Paths.Add("{param-with-dashes}\\existing-segment", new()
{
Operations = new Dictionary<OperationType, OpenApiOperation> {
{ OperationType.Get, new() {
Parameters = new List<OpenApiParameter> {
Parameters = [
new() {
Name = "param-with-dashes",
In = ParameterLocation.Path,
Expand All @@ -155,12 +155,12 @@ public void GetUrlTemplateSelectsDistinctQueryParameters()
},
Style = ParameterStyle.Simple,
}
}
]
}
},
{
OperationType.Put, new() {
Parameters = new List<OpenApiParameter> {
Parameters = [
new() {
Name = "param-with-dashes",
In = ParameterLocation.Path,
Expand All @@ -178,7 +178,7 @@ public void GetUrlTemplateSelectsDistinctQueryParameters()
},
Style = ParameterStyle.Simple,
}
}
]
}
}
}
Expand All @@ -187,19 +187,280 @@ public void GetUrlTemplateSelectsDistinctQueryParameters()
Assert.Equal("{+baseurl}/{param%2Dwith%2Ddashes}/existing-segment{?%24select}", node.Children.First().Value.GetUrlTemplate());
// the query parameters will be decoded by a middleware at runtime before the request is executed
}
[Fact]
public void GeneratesRequiredQueryParametersAndOptionalMixInPathItem()
{
var doc = new OpenApiDocument
{
Paths = [],
};
doc.Paths.Add("users\\{id}\\manager", new()
{
Parameters = {
new OpenApiParameter {
Name = "id",
In = ParameterLocation.Path,
Required = true,
Schema = new OpenApiSchema {
Type = "string"
}
},
new OpenApiParameter {
Name = "filter",
In = ParameterLocation.Query,
Required = false,
Schema = new OpenApiSchema {
Type = "string"
}
},
new OpenApiParameter {
Name = "apikey",
In = ParameterLocation.Query,
Required = true,
Schema = new OpenApiSchema {
Type = "string"
}
}
},
Operations = new Dictionary<OperationType, OpenApiOperation> {
{ OperationType.Get, new() {
}
},
}
});
var node = OpenApiUrlTreeNode.Create(doc, Label);
Assert.Equal("{+baseurl}/users/{id}/manager?apikey={apikey}{&filter*}", node.Children.First().Value.GetUrlTemplate());
}
[Fact]
public void GeneratesRequiredQueryParametersAndOptionalMixInOperation()
{
var doc = new OpenApiDocument
{
Paths = [],
};
doc.Paths.Add("users\\{id}\\manager", new()
{
Operations = new Dictionary<OperationType, OpenApiOperation> {
{ OperationType.Get, new() {
Parameters = {
new OpenApiParameter {
Name = "id",
In = ParameterLocation.Path,
Required = true,
Schema = new OpenApiSchema {
Type = "string"
}
},
new OpenApiParameter {
Name = "filter",
In = ParameterLocation.Query,
Required = false,
Schema = new OpenApiSchema {
Type = "string"
}
},
new OpenApiParameter {
Name = "apikey",
In = ParameterLocation.Query,
Required = true,
Schema = new OpenApiSchema {
Type = "string"
}
}
},
}
},
}
});
var node = OpenApiUrlTreeNode.Create(doc, Label);
Assert.Equal("{+baseurl}/users/{id}/manager?apikey={apikey}{&filter*}", node.Children.First().Value.GetUrlTemplate());
}
[Fact]
public void GeneratesOnlyOptionalQueryParametersInPathItem()
{
var doc = new OpenApiDocument
{
Paths = [],
};
doc.Paths.Add("users\\{id}\\manager", new()
{
Parameters = {
new OpenApiParameter {
Name = "id",
In = ParameterLocation.Path,
Required = true,
Schema = new OpenApiSchema {
Type = "string"
}
},
new OpenApiParameter {
Name = "filter",
In = ParameterLocation.Query,
Required = false,
Schema = new OpenApiSchema {
Type = "string"
}
},
new OpenApiParameter {
Name = "apikey",
In = ParameterLocation.Query,
Schema = new OpenApiSchema {
Type = "string"
}
}
},
Operations = new Dictionary<OperationType, OpenApiOperation> {
{ OperationType.Get, new() {
}
},
}
});
var node = OpenApiUrlTreeNode.Create(doc, Label);
Assert.Equal("{+baseurl}/users/{id}/manager{?apikey*,filter*}", node.Children.First().Value.GetUrlTemplate());
}
[Fact]
public void GeneratesOnlyOptionalQueryParametersInOperation()
{
var doc = new OpenApiDocument
{
Paths = [],
};
doc.Paths.Add("users\\{id}\\manager", new()
{
Operations = new Dictionary<OperationType, OpenApiOperation> {
{ OperationType.Get, new() {
Parameters = {
new OpenApiParameter {
Name = "id",
In = ParameterLocation.Path,
Required = true,
Schema = new OpenApiSchema {
Type = "string"
}
},
new OpenApiParameter {
Name = "filter",
In = ParameterLocation.Query,
Schema = new OpenApiSchema {
Type = "string"
}
},
new OpenApiParameter {
Name = "apikey",
In = ParameterLocation.Query,
Schema = new OpenApiSchema {
Type = "string"
}
}
},
}
},
}
});
var node = OpenApiUrlTreeNode.Create(doc, Label);
Assert.Equal("{+baseurl}/users/{id}/manager{?apikey*,filter*}", node.Children.First().Value.GetUrlTemplate());
}
[Fact]
public void GeneratesOnlyRequiredQueryParametersInPathItem()
{
var doc = new OpenApiDocument
{
Paths = [],
};
doc.Paths.Add("users\\{id}\\manager", new()
{
Parameters = {
new OpenApiParameter {
Name = "id",
In = ParameterLocation.Path,
Required = true,
Schema = new OpenApiSchema {
Type = "string"
}
},
new OpenApiParameter {
Name = "filter",
In = ParameterLocation.Query,
Required = true,
Schema = new OpenApiSchema {
Type = "string"
}
},
new OpenApiParameter {
Name = "apikey",
Required = true,
In = ParameterLocation.Query,
Schema = new OpenApiSchema {
Type = "string"
}
}
},
Operations = new Dictionary<OperationType, OpenApiOperation> {
{ OperationType.Get, new() {
}
},
}
});
var node = OpenApiUrlTreeNode.Create(doc, Label);
Assert.Equal("{+baseurl}/users/{id}/manager?apikey={apikey}&filter={filter}", node.Children.First().Value.GetUrlTemplate());
}
[Fact]
public void GeneratesOnlyRequiredQueryParametersInOperation()
{
var doc = new OpenApiDocument
{
Paths = [],
};
doc.Paths.Add("users\\{id}\\manager", new()
{
Operations = new Dictionary<OperationType, OpenApiOperation> {
{ OperationType.Get, new() {
Parameters = {
new OpenApiParameter {
Name = "id",
In = ParameterLocation.Path,
Required = true,
Schema = new OpenApiSchema {
Type = "string"
}
},
new OpenApiParameter {
Name = "filter",
Required = true,
In = ParameterLocation.Query,
Schema = new OpenApiSchema {
Type = "string"
}
},
new OpenApiParameter {
Name = "apikey",
Required = true,
In = ParameterLocation.Query,
Schema = new OpenApiSchema {
Type = "string"
}
}
},
}
},
}
});
var node = OpenApiUrlTreeNode.Create(doc, Label);
Assert.Equal("{+baseurl}/users/{id}/manager?apikey={apikey}&filter={filter}", node.Children.First().Value.GetUrlTemplate());
}

[Fact]
public void GetUrlTemplateCleansInvalidParameters()
{
var doc = new OpenApiDocument
{
Paths = new(),
Paths = [],
};
doc.Paths.Add("{param-with-dashes}\\existing-segment", new()
{
Operations = new Dictionary<OperationType, OpenApiOperation> {
{ OperationType.Get, new() {
Parameters = new List<OpenApiParameter> {
Parameters = [
new() {
Name = "param-with-dashes",
In = ParameterLocation.Path,
Expand Down Expand Up @@ -241,13 +502,13 @@ public void GetUrlTemplateCleansInvalidParameters()
},
Style = ParameterStyle.Simple,
}
}
]
}
}
}
});
var node = OpenApiUrlTreeNode.Create(doc, Label);
Assert.Equal("{+baseurl}/{param%2Dwith%2Ddashes}/existing-segment{?%24select,api%2Dversion,api%7Etopic,api%2Eencoding}", node.Children.First().Value.GetUrlTemplate());
Assert.Equal("{+baseurl}/{param%2Dwith%2Ddashes}/existing-segment{?%24select,api%2Dversion,api%2Eencoding,api%7Etopic}", node.Children.First().Value.GetUrlTemplate());
// the query parameters will be decoded by a middleware at runtime before the request is executed
}
[InlineData("\\reviews\\search.json", "reviews.searchJson")]
Expand Down

0 comments on commit 4ed90ca

Please sign in to comment.