diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs index a7105851..879f4727 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiParameterGenerator.cs @@ -34,8 +34,8 @@ public static void AddParametersToDocument(this ODataContext context, OpenApiDoc // It allows defining query options and headers that can be reused across operations of the service. // The value of parameters is a map of Parameter Objects. - document.AddComponent("top", CreateTop(context.Settings.TopExample)); - document.AddComponent("skip", CreateSkip()); + document.AddComponent("top", CreateTop(context.Settings.TopExample, context.Settings.UseInt32ForPaginationParameters)); + document.AddComponent("skip", CreateSkip(context.Settings.UseInt32ForPaginationParameters)); document.AddComponent("count", CreateCount()); document.AddComponent("filter", CreateFilter()); document.AddComponent("search", CreateSearch()); @@ -886,7 +886,7 @@ public static void AppendParameter(this IList parameters, IOp } // #top - private static OpenApiParameter CreateTop(int topExample) + private static OpenApiParameter CreateTop(int topExample, bool useInt32Format = false) { return new OpenApiParameter { @@ -896,7 +896,7 @@ private static OpenApiParameter CreateTop(int topExample) Schema = new OpenApiSchema { Type = JsonSchemaType.Number, - Format = "int64", + Format = useInt32Format ? "int32" : "int64", Minimum = "0", }, Example = topExample, @@ -906,7 +906,7 @@ private static OpenApiParameter CreateTop(int topExample) } // $skip - private static OpenApiParameter CreateSkip() + private static OpenApiParameter CreateSkip(bool useInt32Format = false) { return new OpenApiParameter { @@ -916,7 +916,7 @@ private static OpenApiParameter CreateSkip() Schema = new OpenApiSchema { Type = JsonSchemaType.Number, - Format = "int64", + Format = useInt32Format ? "int32" : "int64", Minimum = "0", }, Style = ParameterStyle.Form, diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs index 1bec08b1..fdbf0333 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs @@ -195,7 +195,8 @@ public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOp } if (context.Settings.EnableCount) { - baseSchema.Properties.Add(ODataConstants.OdataCount.Key, ODataConstants.OdataCount.Value); + var odataCount = ODataConstants.CreateOdataCount(context.Settings.UseInt32ForCountResponses); + baseSchema.Properties.Add(odataCount.Key, odataCount.Value); } schema = baseSchema; } diff --git a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs index df83013b..0fbd9541 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs @@ -48,7 +48,7 @@ public static void AddSchemasToDocument(this ODataContext context, OpenApiDocume if(context.Settings.EnableDollarCountPath) document.AddComponent(Constants.DollarCountSchemaName, new OpenApiSchema { Type = JsonSchemaType.Number, - Format = "int64" + Format = context.Settings.UseInt32ForCountResponses ? "int32" : "int64" }); if(context.HasAnyNonContainedCollections()) @@ -101,7 +101,10 @@ public static void AddSchemasToDocument(this ODataContext context, OpenApiDocume responseSchema.Properties ??= new Dictionary(); if (context.Settings.EnableCount) - responseSchema.Properties.Add(ODataConstants.OdataCount.Key, ODataConstants.OdataCount.Value); + { + var odataCount = ODataConstants.CreateOdataCount(context.Settings.UseInt32ForCountResponses); + responseSchema.Properties.Add(odataCount.Key, odataCount.Value); + } if (context.Settings.EnablePagination) responseSchema.Properties.Add(ODataConstants.OdataNextLink.Key, ODataConstants.OdataNextLink.Value); } @@ -261,10 +264,13 @@ private static OpenApiSchema CreateCollectionSchema(ODataContext context, IOpenA baseSchema.Properties.Add(ODataConstants.OdataNextLink.Key, ODataConstants.OdataNextLink.Value); if (context.Settings.EnableCount) - baseSchema.Properties.Add(ODataConstants.OdataCount.Key, ODataConstants.OdataCount.Value); + { + var odataCount = ODataConstants.CreateOdataCount(context.Settings.UseInt32ForCountResponses); + baseSchema.Properties.Add(odataCount.Key, odataCount.Value); + } collectionSchema = baseSchema; - } + } } else { diff --git a/src/Microsoft.OpenApi.OData.Reader/OData/ODataConstants.cs b/src/Microsoft.OpenApi.OData.Reader/OData/ODataConstants.cs index d8aec5fb..829b3184 100644 --- a/src/Microsoft.OpenApi.OData.Reader/OData/ODataConstants.cs +++ b/src/Microsoft.OpenApi.OData.Reader/OData/ODataConstants.cs @@ -29,6 +29,18 @@ internal static class ODataConstants /// public readonly static KeyValuePair OdataCount = new("@odata.count", new OpenApiSchema { Type = JsonSchemaType.Number | JsonSchemaType.Null, Format = "int64"}); + /// + /// Creates an @odata.count KeyValue pair with the specified format. + /// + public static KeyValuePair CreateOdataCount(bool useInt32Format) + { + return new("@odata.count", new OpenApiSchema + { + Type = JsonSchemaType.Number | JsonSchemaType.Null, + Format = useInt32Format ? "int32" : "int64" + }); + } + /// /// @odata.deltaLink KeyValue pair /// diff --git a/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs b/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs index a7021c44..7effbc0c 100644 --- a/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs +++ b/src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs @@ -337,6 +337,16 @@ public string? PathPrefix /// public int ComposableFunctionsExpansionDepth { get; set; } = 1; + /// + /// Gets/Sets a value indicating whether common OData number parameters ($top, $skip) should use int32 format instead of int64. + /// + public bool UseInt32ForPaginationParameters { get; set; } = false; + + /// + /// Gets/Sets a value indicating whether the $count response schema should use int32 format instead of int64. + /// + public bool UseInt32ForCountResponses { get; set; } = false; + internal OpenApiConvertSettings Clone() { var newSettings = new OpenApiConvertSettings @@ -392,7 +402,9 @@ internal OpenApiConvertSettings Clone() SemVerVersion = this.SemVerVersion, EnableAliasForOperationSegments = this.EnableAliasForOperationSegments, UseStringArrayForQueryOptionsSchema = this.UseStringArrayForQueryOptionsSchema, - ComposableFunctionsExpansionDepth = this.ComposableFunctionsExpansionDepth + ComposableFunctionsExpansionDepth = this.ComposableFunctionsExpansionDepth, + UseInt32ForPaginationParameters = this.UseInt32ForPaginationParameters, + UseInt32ForCountResponses = this.UseInt32ForCountResponses }; return newSettings; diff --git a/src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt index b175f6a1..01b1978b 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi.OData.Reader/PublicAPI.Unshipped.txt @@ -201,6 +201,10 @@ Microsoft.OpenApi.OData.OpenApiConvertSettings.RoutePathPrefixProvider.get -> Mi Microsoft.OpenApi.OData.OpenApiConvertSettings.RoutePathPrefixProvider.set -> void Microsoft.OpenApi.OData.OpenApiConvertSettings.SemVerVersion.get -> string! Microsoft.OpenApi.OData.OpenApiConvertSettings.SemVerVersion.set -> void +Microsoft.OpenApi.OData.OpenApiConvertSettings.UseInt32ForPaginationParameters.get -> bool +Microsoft.OpenApi.OData.OpenApiConvertSettings.UseInt32ForPaginationParameters.set -> void +Microsoft.OpenApi.OData.OpenApiConvertSettings.UseInt32ForCountResponses.get -> bool +Microsoft.OpenApi.OData.OpenApiConvertSettings.UseInt32ForCountResponses.set -> void Microsoft.OpenApi.OData.OpenApiConvertSettings.ServiceRoot.get -> System.Uri! Microsoft.OpenApi.OData.OpenApiConvertSettings.ServiceRoot.set -> void Microsoft.OpenApi.OData.OpenApiConvertSettings.ShowExternalDocs.get -> bool diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiParameterGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiParameterGeneratorTests.cs index ba60bbd8..5e3f86bc 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiParameterGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiParameterGeneratorTests.cs @@ -655,5 +655,35 @@ public static IEdmModel GetEdmModel() Assert.True(result); return model; } + + [Theory] + [InlineData(true, "int32")] + [InlineData(false, "int64")] + public async Task CreateParametersRespectsUseInt32ForPaginationParameters(bool useInt32, string expectedFormat) + { + // Arrange + IEdmModel model = EdmCoreModel.Instance; + OpenApiConvertSettings settings = new() + { + UseInt32ForPaginationParameters = useInt32 + }; + ODataContext context = new ODataContext(model, settings); + OpenApiDocument openApiDocument = new(); + + // Act + context.AddParametersToDocument(openApiDocument); + var parameters = openApiDocument.Components.Parameters; + + // Assert + Assert.NotNull(parameters); + var topParam = parameters.First(p => p.Key == "top").Value; + var skipParam = parameters.First(p => p.Key == "skip").Value; + + var topJson = JsonNode.Parse(await topParam.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0)); + var skipJson = JsonNode.Parse(await skipParam.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0)); + + Assert.Equal(expectedFormat, topJson["schema"]["format"]?.GetValue()); + Assert.Equal(expectedFormat, skipJson["schema"]["format"]?.GetValue()); + } } } \ No newline at end of file diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs index 630e59f2..14bd3156 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Generator/OpenApiSchemaGeneratorTests.cs @@ -1161,5 +1161,30 @@ public async Task NonNullableUntypedPropertyWorks() Assert.Equal("{ }", json); } + + [Theory] + [InlineData(true, "int32")] + [InlineData(false, "int64")] + public void DollarCountSchemaRespectsUseInt32ForCountResponses(bool useInt32, string expectedFormat) + { + // Arrange + IEdmModel model = EdmModelHelper.TripServiceModel; + OpenApiDocument openApiDocument = new(); + OpenApiConvertSettings settings = new() + { + EnableDollarCountPath = true, + UseInt32ForCountResponses = useInt32 + }; + ODataContext context = new(model, settings); + + // Act + context.AddSchemasToDocument(openApiDocument); + + // Assert + Assert.True(openApiDocument.Components.Schemas.TryGetValue(Constants.DollarCountSchemaName, out var countSchema)); + Assert.NotNull(countSchema); + Assert.Equal(JsonSchemaType.Number, countSchema.Type); + Assert.Equal(expectedFormat, countSchema.Format); + } } }