From 04a9525ca002e5667235f787746c1c08e567cf2b Mon Sep 17 00:00:00 2001 From: Vishrut Shah Date: Thu, 2 Jun 2016 12:03:39 -0700 Subject: [PATCH] x-ms-parameter-grouping for Ruby & Azure.Ruby Generator --- .../RspecTests/paging_spec.rb | 4 +- .../RspecTests/parameter_grouping_spec.rb | 110 ++++++++++++++++++ .../Ruby/Ruby/ClientModelExtensions.cs | 5 - .../Generators/Ruby/Ruby/RubyCodeGenerator.cs | 1 + .../TemplateModels/MethodTemplateModel.cs | 95 ++++++++++++++- .../Ruby/Ruby/Templates/MethodTemplate.cshtml | 7 +- .../Ruby/petstore/swagger_petstore.rb | 44 +++++++ gulpfile.js | 3 +- 8 files changed, 257 insertions(+), 12 deletions(-) create mode 100644 AutoRest/Generators/Ruby/Ruby.Tests/RspecTests/parameter_grouping_spec.rb diff --git a/AutoRest/Generators/Ruby/Azure.Ruby.Tests/RspecTests/paging_spec.rb b/AutoRest/Generators/Ruby/Azure.Ruby.Tests/RspecTests/paging_spec.rb index 8a53122d34fe7..a6e6ea41726bf 100644 --- a/AutoRest/Generators/Ruby/Azure.Ruby.Tests/RspecTests/paging_spec.rb +++ b/AutoRest/Generators/Ruby/Azure.Ruby.Tests/RspecTests/paging_spec.rb @@ -53,7 +53,9 @@ end it 'should get multiple pages with offset' do - result = @client.paging.get_multiple_pages_with_offset_async(100).value! + options = PagingModule::Models::PagingGetMultiplePagesWithOffsetOptions.new + options.offset = 100 + result = @client.paging.get_multiple_pages_with_offset_async(options).value! expect(result.response.status).to eq(200) expect(result.body.next_link).not_to be_nil diff --git a/AutoRest/Generators/Ruby/Ruby.Tests/RspecTests/parameter_grouping_spec.rb b/AutoRest/Generators/Ruby/Ruby.Tests/RspecTests/parameter_grouping_spec.rb new file mode 100644 index 0000000000000..a17beabd53032 --- /dev/null +++ b/AutoRest/Generators/Ruby/Ruby.Tests/RspecTests/parameter_grouping_spec.rb @@ -0,0 +1,110 @@ +# encoding: utf-8 + +$: << 'RspecTests/Generated/parameter_grouping' + +require 'rspec' +require 'azure_parameter_grouping' + +include ParameterGroupingModule +include ParameterGroupingModule::Models + +describe 'ParameterGrouping' do + before(:all) do + @base_url = ENV['StubServerURI'] + dummyToken = 'dummy123@query343423' + @credentials = MsRest::TokenCredentials.new(dummyToken) + @client = AutoRestParameterGroupingTestService.new(@credentials, @base_url) + + @body = 1234 + @header = 'header' + @query = 21 + @path = 'path' + end + + it 'should accept valid required parameters' do + required_parameters = ParameterGroupingPostRequiredParameters.new + required_parameters.body = @body + required_parameters.custom_header = @header + required_parameters.query = @query + required_parameters.path = @path + + result = @client.parameter_grouping.post_required_async(required_parameters).value! + + expect(result.response.status).to eq(200) + end + + it 'should accept required parameters but null optional parameters' do + required_parameters = ParameterGroupingPostRequiredParameters.new + required_parameters.body = @body + required_parameters.path = @path + + result = @client.parameter_grouping.post_required_async(required_parameters).value! + + expect(result.response.status).to eq(200) + end + + it 'should reject required parameters with missing required property' do + required_parameters = ParameterGroupingPostRequiredParameters.new + required_parameters.path = @path + + expect { @client.parameter_grouping.post_required_async(required_parameters).value! }.to raise_error(MsRest::ValidationError) + end + + it 'should reject null required parameters' do + required_parameters = ParameterGroupingPostRequiredParameters.new + required_parameters.path = nil + + expect { @client.parameter_grouping.post_required_async(required_parameters).value! }.to raise_error(MsRest::ValidationError) + end + + it 'should accept valid optional parameters' do + optional_parameters = ParameterGroupingPostOptionalParameters.new + optional_parameters.custom_header = @header + optional_parameters.query = @query + + result = @client.parameter_grouping.post_optional_async(optional_parameters).value! + expect(result.response.status).to eq(200) + end + + it 'should accept null optional parameters' do + result = @client.parameter_grouping.post_optional_async(nil).value! + expect(result.response.status).to eq(200) + end + + it 'should allow multiple parameter groups' do + first_parameter_group = FirstParameterGroup.new + second_parameter_group = ParameterGroupingPostMultiParamGroupsSecondParamGroup.new + + first_parameter_group.header_one = @header + first_parameter_group.query_one = @query + + second_parameter_group.header_two = 'header2' + second_parameter_group.query_two = 42 + + result = @client.parameter_grouping.post_multi_param_groups_async(first_parameter_group, second_parameter_group).value! + expect(result.response.status).to eq(200) + end + + it 'should allow multiple parameter groups with some defaults omitted' do + first_parameter_group = FirstParameterGroup.new + second_parameter_group = ParameterGroupingPostMultiParamGroupsSecondParamGroup.new + + first_parameter_group.header_one = @header + + second_parameter_group.query_two = 42 + + result = @client.parameter_grouping.post_multi_param_groups_async(first_parameter_group, second_parameter_group).value! + expect(result.response.status).to eq(200) + end + + # This test has nothing to do with sharing of the FirstParameterGroup. It's included for test coverage + it 'should allow parameter group objects to be shared between operations' do + first_parameter_group = FirstParameterGroup.new + + first_parameter_group.header_one = @header + first_parameter_group.query_one = 42 + + result = @client.parameter_grouping.post_shared_parameter_group_object_async(first_parameter_group).value! + expect(result.response.status).to eq(200) + end +end \ No newline at end of file diff --git a/AutoRest/Generators/Ruby/Ruby/ClientModelExtensions.cs b/AutoRest/Generators/Ruby/Ruby/ClientModelExtensions.cs index 05385f5a34fad..3268f6a2e84f4 100644 --- a/AutoRest/Generators/Ruby/Ruby/ClientModelExtensions.cs +++ b/AutoRest/Generators/Ruby/Ruby/ClientModelExtensions.cs @@ -409,11 +409,6 @@ private static string AddMetaData(this IType type, string serializedName, IParam throw new ArgumentNullException(nameof(type)); } - if (serializedName == null) - { - throw new ArgumentNullException(nameof(serializedName)); - } - IndentedStringBuilder builder = new IndentedStringBuilder(" "); Dictionary constraints = null; diff --git a/AutoRest/Generators/Ruby/Ruby/RubyCodeGenerator.cs b/AutoRest/Generators/Ruby/Ruby/RubyCodeGenerator.cs index 114540a6c5372..03c68c94927d9 100644 --- a/AutoRest/Generators/Ruby/Ruby/RubyCodeGenerator.cs +++ b/AutoRest/Generators/Ruby/Ruby/RubyCodeGenerator.cs @@ -114,6 +114,7 @@ public override string ImplementationFileExtension /// public override void NormalizeClientModel(ServiceClient serviceClient) { + ParameterGroupExtensionHelper.AddParameterGroups(serviceClient); Extensions.ProcessParameterizedHost(serviceClient, Settings); PopulateAdditionalProperties(serviceClient); CodeNamer.NormalizeClientModel(serviceClient); diff --git a/AutoRest/Generators/Ruby/Ruby/TemplateModels/MethodTemplateModel.cs b/AutoRest/Generators/Ruby/Ruby/TemplateModels/MethodTemplateModel.cs index 0ef44d300a900..0cc311711f72e 100644 --- a/AutoRest/Generators/Ruby/Ruby/TemplateModels/MethodTemplateModel.cs +++ b/AutoRest/Generators/Ruby/Ruby/TemplateModels/MethodTemplateModel.cs @@ -28,6 +28,10 @@ public MethodTemplateModel(Method source, ServiceClient serviceClient) this.LoadFrom(source); ParameterTemplateModels = new List(); source.Parameters.ForEach(p => ParameterTemplateModels.Add(new ParameterTemplateModel(p))); + + LogicalParameterTemplateModels = new List(); + source.LogicalParameters.ForEach(p => LogicalParameterTemplateModels.Add(new ParameterTemplateModel(p))); + ServiceClient = serviceClient; } @@ -148,7 +152,7 @@ public virtual IEnumerable SkipEncodingPathParams /// public virtual IEnumerable AllPathParams { - get { return ParameterTemplateModels.Where(p => p.Location == ParameterLocation.Path); } + get { return LogicalParameterTemplateModels.Where(p => p.Location == ParameterLocation.Path); } } /// @@ -172,7 +176,10 @@ public virtual IEnumerable EncodingQueryParams /// public virtual IEnumerable AllQueryParams { - get { return ParameterTemplateModels.Where(p => p.Location == ParameterLocation.Query); } + get + { + return LogicalParameterTemplateModels.Where(p => p.Location == ParameterLocation.Query); + } } /// @@ -211,6 +218,11 @@ public virtual string SetDefaultHeaders /// public List ParameterTemplateModels { get; private set; } + /// + /// Gets the list of logical method paramater templates. + /// + private List LogicalParameterTemplateModels { get; set; } + /// /// Gets the list of parameter which need to be included into HTTP header. /// @@ -326,7 +338,7 @@ public IEnumerable LocalParameters /// public ParameterTemplateModel RequestBody { - get { return ParameterTemplateModels.FirstOrDefault(p => p.Location == ParameterLocation.Body); } + get { return LogicalParameterTemplateModels.FirstOrDefault(p => p.Location == ParameterLocation.Body); } } /// @@ -435,6 +447,58 @@ public virtual string BuildUrl(string variableName) return builder.ToString(); } + /// + /// Build parameter mapping from parameter grouping transformation. + /// + /// + public virtual string BuildInputParameterMappings() + { + var builder = new IndentedStringBuilder(" "); + if (InputParameterTransformation.Count > 0) + { + builder.Indent(); + foreach (var transformation in InputParameterTransformation) + { + if (transformation.OutputParameter.Type is CompositeType && + transformation.OutputParameter.IsRequired) + { + builder.AppendLine("{0} = {1}.new", + transformation.OutputParameter.Name, + transformation.OutputParameter.Type.Name); + } + else + { + builder.AppendLine("{0} = nil", transformation.OutputParameter.Name); + } + } + foreach (var transformation in InputParameterTransformation) + { + builder.AppendLine("unless {0}", BuildNullCheckExpression(transformation)) + .AppendLine().Indent(); + var outputParameter = transformation.OutputParameter; + if (transformation.ParameterMappings.Any(m => !string.IsNullOrEmpty(m.OutputParameterProperty)) && + transformation.OutputParameter.Type is CompositeType) + { + //required outputParameter is initialized at the time of declaration + if (!transformation.OutputParameter.IsRequired) + { + builder.AppendLine("{0} = {1}.new", + transformation.OutputParameter.Name, + transformation.OutputParameter.Type.Name); + } + } + + foreach (var mapping in transformation.ParameterMappings) + { + builder.AppendLine("{0}{1}", transformation.OutputParameter.Name, mapping); + } + + builder.Outdent().AppendLine("end"); + } + } + return builder.ToString(); + } + /// /// Gets the formatted status code. /// @@ -556,5 +620,30 @@ public string GetDeserializationString(IType type, string valueReference = "resu return builder.ToString(); } + + /// + /// Builds null check expression for the given . + /// + /// ParameterTransformation for which to build null check expression. + /// + private static string BuildNullCheckExpression(ParameterTransformation transformation) + { + if (transformation == null) + { + throw new ArgumentNullException("transformation"); + } + if (transformation.ParameterMappings.Count == 1) + { + return string.Format(CultureInfo.InvariantCulture, + "{0}.nil?",transformation.ParameterMappings[0].InputParameter.Name); + } + else + { + return string.Join(" && ", + transformation.ParameterMappings.Select(m => + string.Format(CultureInfo.InvariantCulture, + "{0}.nil?", m.InputParameter.Name))); + } + } } } \ No newline at end of file diff --git a/AutoRest/Generators/Ruby/Ruby/Templates/MethodTemplate.cshtml b/AutoRest/Generators/Ruby/Ruby/Templates/MethodTemplate.cshtml index d4efe8f433b75..a7e125b6339c5 100644 --- a/AutoRest/Generators/Ruby/Ruby/Templates/MethodTemplate.cshtml +++ b/AutoRest/Generators/Ruby/Ruby/Templates/MethodTemplate.cshtml @@ -97,13 +97,16 @@ def @(Model.Name)_async(@(Model.MethodParameterDeclaration)) @:@(parameter.Name) = @(parameter.DefaultValue) } } +@EmptyLine +@(Model.BuildInputParameterMappings()) +@EmptyLine request_headers = {} -@if (Model.Parameters.Any(p => p.Location == ParameterLocation.Header)) +@if (Model.LogicalParameters.Any(p => p.Location == ParameterLocation.Header)) { @EmptyLine @:# Set Headers @:@(Model.SetDefaultHeaders) - foreach (var parameter in Model.Parameters.Where(p => p.Location == ParameterLocation.Header)) + foreach (var parameter in Model.LogicalParameters.Where(p => p.Location == ParameterLocation.Header)) { if (parameter.SerializedName.ToLower() == "Content-Type".ToLower()) { diff --git a/Samples/petstore/Ruby/petstore/swagger_petstore.rb b/Samples/petstore/Ruby/petstore/swagger_petstore.rb index 1aad859034fd5..78f55ca18344e 100644 --- a/Samples/petstore/Ruby/petstore/swagger_petstore.rb +++ b/Samples/petstore/Ruby/petstore/swagger_petstore.rb @@ -65,6 +65,8 @@ def add_pet_using_byte_array_with_http_info(body = nil, custom_headers = nil) # @return [Concurrent::Promise] Promise object which holds the HTTP response. # def add_pet_using_byte_array_async(body = nil, custom_headers = nil) + + request_headers = {} request_headers['Content-Type'] = 'application/json; charset=utf-8' @@ -156,6 +158,8 @@ def add_pet_with_http_info(body = nil, custom_headers = nil) # @return [Concurrent::Promise] Promise object which holds the HTTP response. # def add_pet_async(body = nil, custom_headers = nil) + + request_headers = {} request_headers['Content-Type'] = 'application/json; charset=utf-8' @@ -232,6 +236,8 @@ def update_pet_with_http_info(body = nil, custom_headers = nil) # @return [Concurrent::Promise] Promise object which holds the HTTP response. # def update_pet_async(body = nil, custom_headers = nil) + + request_headers = {} request_headers['Content-Type'] = 'application/json; charset=utf-8' @@ -318,6 +324,8 @@ def find_pets_by_status_with_http_info(status = nil, custom_headers = nil) # @return [Concurrent::Promise] Promise object which holds the HTTP response. # def find_pets_by_status_async(status = nil, custom_headers = nil) + + request_headers = {} path_template = '/pet/findByStatus' options = { @@ -420,6 +428,8 @@ def find_pets_by_tags_with_http_info(tags = nil, custom_headers = nil) # @return [Concurrent::Promise] Promise object which holds the HTTP response. # def find_pets_by_tags_async(tags = nil, custom_headers = nil) + + request_headers = {} path_template = '/pet/findByTags' options = { @@ -523,6 +533,8 @@ def find_pets_with_byte_array_with_http_info(pet_id, custom_headers = nil) # def find_pets_with_byte_array_async(pet_id, custom_headers = nil) fail ArgumentError, 'pet_id is nil' if pet_id.nil? + + request_headers = {} path_template = '/pet/{petId}' options = { @@ -618,6 +630,8 @@ def get_pet_by_id_with_http_info(pet_id, custom_headers = nil) # def get_pet_by_id_async(pet_id, custom_headers = nil) fail ArgumentError, 'pet_id is nil' if pet_id.nil? + + request_headers = {} path_template = '/pet/{petId}' options = { @@ -703,6 +717,8 @@ def update_pet_with_form_with_http_info(pet_id, name = nil, status = nil, custom # def update_pet_with_form_async(pet_id, name = nil, status = nil, custom_headers = nil) fail ArgumentError, 'pet_id is nil' if pet_id.nil? + + request_headers = {} path_template = '/pet/{petId}' options = { @@ -775,6 +791,8 @@ def delete_pet_with_http_info(pet_id, api_key = nil, custom_headers = nil) # def delete_pet_async(pet_id, api_key = nil, custom_headers = nil) fail ArgumentError, 'pet_id is nil' if pet_id.nil? + + request_headers = {} # Set Headers @@ -853,6 +871,8 @@ def upload_file_with_http_info(pet_id, additional_metadata = nil, file = nil, cu # def upload_file_async(pet_id, additional_metadata = nil, file = nil, custom_headers = nil) fail ArgumentError, 'pet_id is nil' if pet_id.nil? + + request_headers = {} path_template = '/pet/{petId}/uploadImage' options = { @@ -925,6 +945,8 @@ def get_inventory_with_http_info(custom_headers = nil) # @return [Concurrent::Promise] Promise object which holds the HTTP response. # def get_inventory_async(custom_headers = nil) + + request_headers = {} path_template = '/store/inventory' options = { @@ -1016,6 +1038,8 @@ def place_order_with_http_info(body = nil, custom_headers = nil) # @return [Concurrent::Promise] Promise object which holds the HTTP response. # def place_order_async(body = nil, custom_headers = nil) + + request_headers = {} request_headers['Content-Type'] = 'application/json; charset=utf-8' @@ -1113,6 +1137,8 @@ def get_order_by_id_with_http_info(order_id, custom_headers = nil) # def get_order_by_id_async(order_id, custom_headers = nil) fail ArgumentError, 'order_id is nil' if order_id.nil? + + request_headers = {} path_template = '/store/order/{orderId}' options = { @@ -1201,6 +1227,8 @@ def delete_order_with_http_info(order_id, custom_headers = nil) # def delete_order_async(order_id, custom_headers = nil) fail ArgumentError, 'order_id is nil' if order_id.nil? + + request_headers = {} path_template = '/store/order/{orderId}' options = { @@ -1275,6 +1303,8 @@ def create_user_with_http_info(body = nil, custom_headers = nil) # @return [Concurrent::Promise] Promise object which holds the HTTP response. # def create_user_async(body = nil, custom_headers = nil) + + request_headers = {} request_headers['Content-Type'] = 'application/json; charset=utf-8' @@ -1351,6 +1381,8 @@ def create_users_with_array_input_with_http_info(body = nil, custom_headers = ni # @return [Concurrent::Promise] Promise object which holds the HTTP response. # def create_users_with_array_input_async(body = nil, custom_headers = nil) + + request_headers = {} request_headers['Content-Type'] = 'application/json; charset=utf-8' @@ -1441,6 +1473,8 @@ def create_users_with_list_input_with_http_info(body = nil, custom_headers = nil # @return [Concurrent::Promise] Promise object which holds the HTTP response. # def create_users_with_list_input_async(body = nil, custom_headers = nil) + + request_headers = {} request_headers['Content-Type'] = 'application/json; charset=utf-8' @@ -1535,6 +1569,8 @@ def login_user_with_http_info(username = nil, password = nil, custom_headers = n # @return [Concurrent::Promise] Promise object which holds the HTTP response. # def login_user_async(username = nil, password = nil, custom_headers = nil) + + request_headers = {} path_template = '/user/login' options = { @@ -1616,6 +1652,8 @@ def logout_user_with_http_info(custom_headers = nil) # @return [Concurrent::Promise] Promise object which holds the HTTP response. # def logout_user_async(custom_headers = nil) + + request_headers = {} path_template = '/user/logout' options = { @@ -1688,6 +1726,8 @@ def get_user_by_name_with_http_info(username, custom_headers = nil) # def get_user_by_name_async(username, custom_headers = nil) fail ArgumentError, 'username is nil' if username.nil? + + request_headers = {} path_template = '/user/{username}' options = { @@ -1776,6 +1816,8 @@ def update_user_with_http_info(username, body = nil, custom_headers = nil) # def update_user_async(username, body = nil, custom_headers = nil) fail ArgumentError, 'username is nil' if username.nil? + + request_headers = {} request_headers['Content-Type'] = 'application/json; charset=utf-8' @@ -1860,6 +1902,8 @@ def delete_user_with_http_info(username, custom_headers = nil) # def delete_user_async(username, custom_headers = nil) fail ArgumentError, 'username is nil' if username.nil? + + request_headers = {} path_template = '/user/{username}' options = { diff --git a/gulpfile.js b/gulpfile.js index 0d1eafceeaaf5..9750f82c8bb89 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -86,7 +86,8 @@ var rubyMappings = { 'http_infrastructure':['../../../TestServer/swagger/httpInfrastructure.json','HttpInfrastructureModule'], 'required_optional':['../../../TestServer/swagger/required-optional.json','RequiredOptionalModule'], 'report':['../../../TestServer/swagger/report.json','ReportModule'], - 'model_flattening':['../../../TestServer/swagger/model-flattening.json', 'ModelFlatteningModule'], + 'model_flattening':['../../../TestServer/swagger/model-flattening.json', 'ModelFlatteningModule'], + 'parameter_grouping':['../../../TestServer/swagger/azure-parameter-grouping.json', 'ParameterGroupingModule'], }; var defaultAzureMappings = {