From 4f327ee8a5b8b8d942ae5c6567f67f10b3fa7d9c Mon Sep 17 00:00:00 2001 From: veronicagg Date: Tue, 12 Jul 2016 15:30:54 -0700 Subject: [PATCH] [In- review] Improving pageable extension adding autopagination (#1211) Improving pageable extension adding autopagination --- .../azure_storage/storage_accounts.rb | 8 +- .../RspecTests/paging_spec.rb | 170 +++++++++++------- .../AzureRubyCodeGenerator.cs | 101 ++++++++++- .../AutoRest.Ruby.Azure/GlobalSuppressions.cs | 5 + .../AzureMethodTemplateModel.cs | 100 +++++++++++ .../TemplateModels/PageTemplateModel.cs | 47 +++++ .../Templates/AzureMethodTemplate.cshtml | 42 ++--- .../Templates/PageModelTemplate.cshtml | 97 ++++++++++ .../TemplateModels/MethodTemplateModel.cs | 33 +++- .../ServiceClientTemplateModel.cs | 2 +- .../Templates/MethodTemplate.cshtml | 15 +- 11 files changed, 505 insertions(+), 115 deletions(-) create mode 100644 src/generator/AutoRest.Ruby.Azure/TemplateModels/PageTemplateModel.cs create mode 100644 src/generator/AutoRest.Ruby.Azure/Templates/PageModelTemplate.cshtml diff --git a/Samples/azure-storage/Azure.Ruby/generated/azure_storage/storage_accounts.rb b/Samples/azure-storage/Azure.Ruby/generated/azure_storage/storage_accounts.rb index 2a9b64dab52ac..d5ff5ef0a54f1 100644 --- a/Samples/azure-storage/Azure.Ruby/generated/azure_storage/storage_accounts.rb +++ b/Samples/azure-storage/Azure.Ruby/generated/azure_storage/storage_accounts.rb @@ -141,12 +141,8 @@ def check_name_availability_async(account_name, custom_headers = nil) # characters in length and use numbers and lower-case letters only. # @param parameters [StorageAccountCreateParameters] The parameters to provide # for the created account. - # @param @client.api_version [String] Client Api Version. - # @param @client.subscription_id [String] Gets subscription credentials which - # uniquely identify Microsoft Azure subscription. The subscription ID forms - # part of the URI for every service call. - # @param @client.accept_language [String] Gets or sets the preferred language - # for the response. + # @param custom_headers [Hash{String => String}] A hash of custom headers that + # will be added to the HTTP request. # # @return [Concurrent::Promise] promise which provides async access to http # response. diff --git a/src/generator/AutoRest.Ruby.Azure.Tests/RspecTests/paging_spec.rb b/src/generator/AutoRest.Ruby.Azure.Tests/RspecTests/paging_spec.rb index e78f08e8c5338..07dab3412b9ba 100644 --- a/src/generator/AutoRest.Ruby.Azure.Tests/RspecTests/paging_spec.rb +++ b/src/generator/AutoRest.Ruby.Azure.Tests/RspecTests/paging_spec.rb @@ -19,102 +19,146 @@ # Paging happy path tests it 'should get single pages' do - result = @client.paging.get_single_pages_async().value! - expect(result.response.status).to eq(200) - expect(result.body.next_link).to be_nil + all_items = @client.paging.get_single_pages() + expect(all_items.count).to eq(1) + expect(all_items.is_a? Array) + expect(all_items.first.is_a? PagingModule::Models::Product) end it 'should get multiple pages' do - result = @client.paging.get_multiple_pages_async().value! - expect(result.response.status).to eq(200) - expect(result.body.next_link).not_to be_nil - - count = 1 - while result.body.next_link != nil do - result = @client.paging.get_multiple_pages_next_async(result.body.next_link).value! - count += 1 - end + all_items = @client.paging.get_multiple_pages() #returns items from all the pages + items_count = all_items.count + expect(items_count).to eq(10) + expect(all_items.is_a? Array) + expect(all_items.first.is_a? PagingModule::Models::Product) + end - expect(count).to eq(10) + it 'should get multiple pages - lazy' do + page = @client.paging.get_multiple_pages_as_lazy() + expect(page.is_a? PagingModule::Models::ProductResult) + expect(page.next_link).not_to be_nil + items = page.values + while page.next_link != nil do + page = page.get_next_page + items.concat(page.values) + end + expect(items.count).to eq(10) end - it 'should get multiple pages with odata kind nextLink' do - result = @client.paging.get_odata_multiple_pages_async().value! - expect(result.response.status).to eq(200) - expect(result.body.odatanext_link).not_to be_nil + it 'should get multiple pages' do + all_items = @client.paging.get_odata_multiple_pages() #returns items from all the pages + items_count = all_items.count + expect(items_count).to eq(10) + expect(all_items.is_a? Array) + expect(all_items.first.is_a? PagingModule::Models::Product) + end - count = 1 - while result.body.odatanext_link != nil do - result = @client.paging.get_odata_multiple_pages_next_async(result.body.odatanext_link).value! - count += 1 + it 'should get multiple pages with odata - lazy ' do + page = @client.paging.get_odata_multiple_pages_as_lazy() + expect(page.is_a? PagingModule::Models::OdataProductResult) + expect(page.odatanext_link).not_to be_nil + items = page.values + while page.odatanext_link != nil do + page = page.get_next_page + items.concat(page.values) end - - expect(count).to eq(10) + expect(items.count).to eq(10) end it 'should get multiple pages with offset' do 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 - - count = 1 - while result.body.next_link != nil do - result = @client.paging.get_multiple_pages_with_offset_next_async(result.body.next_link).value! - count += 1 - end + all_items = @client.paging.get_multiple_pages_with_offset(options) + expect(all_items.count).to eq(10) + expect(all_items.is_a? Array) + expect(all_items.first.is_a? PagingModule::Models::Product) + expect(all_items.last.properties.id).to eq (110) + end - expect(count).to eq(10) - expect(result.body.values.last.properties.id).to eq (110) + it 'should get multiple pages with offset - lazy' do + options = PagingModule::Models::PagingGetMultiplePagesWithOffsetOptions.new + options.offset = 100 + page = @client.paging.get_multiple_pages_with_offset_as_lazy(options) + expect(page.is_a? PagingModule::Models::ProductResult) + expect(page.next_link).not_to be_nil + items = page.values + while page.next_link != nil do + page = page.get_next_page + items.concat(page.values) + end + expect(items.count).to eq(10) + expect(page.values.last.properties.id).to eq (110) end it 'should get multiple pages retry first' do - result = @client.paging.get_multiple_pages_retry_first_async().value! - expect(result.response.status).to eq(200) - expect(result.body.next_link).not_to be_nil - - count = 1 - while result.body.next_link != nil do - result = @client.paging.get_multiple_pages_retry_first_next_async(result.body.next_link).value! - count += 1 + all_items = @client.paging.get_multiple_pages_retry_first() + expect(all_items.count).to eq(10) + expect(all_items.is_a? Array) + expect(all_items.first.is_a? PagingModule::Models::Product) + end + + it 'should get multiple pages retry first - lazy' do + page = @client.paging.get_multiple_pages_retry_first_as_lazy + expect(page.is_a? PagingModule::Models::ProductResult) + expect(page.next_link).not_to be_nil + items = page.values + while page.next_link != nil do + page = page.get_next_page + items.concat(page.values) end + expect(items.count).to eq(10) - expect(count).to eq(10) end it 'should get multiple pages retry second' do - result = @client.paging.get_multiple_pages_retry_second_async().value! - expect(result.response.status).to eq(200) - expect(result.body.next_link).not_to be_nil - - count = 1 - while result.body.next_link != nil do - result = @client.paging.get_multiple_pages_retry_second_next_async(result.body.next_link).value! - count += 1 - end + all_items = @client.paging.get_multiple_pages_retry_second() + expect(all_items.count).to eq(10) + expect(all_items.is_a? Array) + expect(all_items.first.is_a? PagingModule::Models::Product) + end - expect(count).to eq(10) + it 'should get multiple pages retry second - lazy' do + page = @client.paging.get_multiple_pages_retry_second_as_lazy + expect(page.is_a? PagingModule::Models::ProductResult) + expect(page.next_link).not_to be_nil + items = page.values + while page.next_link != nil do + page = page.get_next_page + items.concat(page.values) + end + expect(items.count).to eq(10) end # Paging sad path tests it 'should get single pages failure' do - expect { @client.paging.get_single_pages_failure_async().value! }.to raise_exception(MsRest::HttpOperationError) + expect { @client.paging.get_single_pages_failure_as_lazy }.to raise_exception(MsRest::HttpOperationError) + end + + it 'should get single pages failure' do + expect { @client.paging.get_single_pages_failure() }.to raise_exception(MsRest::HttpOperationError) end it 'should get multiple pages failure' do - result = @client.paging.get_multiple_pages_failure_async().value! - expect(result.response.status).to eq(200) - expect(result.body.next_link).not_to be_nil + expect { @client.paging.get_multiple_pages_failure()}.to raise_exception(MsRest::HttpOperationError) + end + + it 'should get multiple pages failure - lazy' do + page = @client.paging.get_multiple_pages_failure_as_lazy + expect(page.is_a? PagingModule::Models::ProductResult) + expect(page.next_link).not_to be_nil - expect { @client.paging.get_multiple_pages_failure_next_async(result.body.next_link).value! }.to raise_exception(MsRest::HttpOperationError) + expect { page.get_next_page }.to raise_exception(MsRest::HttpOperationError) end it 'should get multiple pages failure URI' do - result = @client.paging.get_multiple_pages_failure_uri_async().value! - expect(result.response.status).to eq(200) - expect(result.body.next_link).not_to be_nil + expect { @client.paging.get_multiple_pages_failure_uri()}.to raise_exception(MsRest::HttpOperationError) + end + + it 'should get multiple pages failure URI - lazy' do + page = @client.paging.get_multiple_pages_failure_uri_as_lazy + expect(page.is_a? PagingModule::Models::ProductResult) + expect(page.next_link).not_to be_nil - expect { @client.paging.get_multiple_pages_failure_uri_next_async(result.body.next_link).value! }.to raise_exception(MsRest::HttpOperationError) + expect { page.get_next_page }.to raise_exception(MsRest::HttpOperationError) end -end \ No newline at end of file +end diff --git a/src/generator/AutoRest.Ruby.Azure/AzureRubyCodeGenerator.cs b/src/generator/AutoRest.Ruby.Azure/AzureRubyCodeGenerator.cs index bf97edbc5bc53..7e28295672066 100644 --- a/src/generator/AutoRest.Ruby.Azure/AzureRubyCodeGenerator.cs +++ b/src/generator/AutoRest.Ruby.Azure/AzureRubyCodeGenerator.cs @@ -1,16 +1,20 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System; using System.IO; using System.Linq; +using System.Collections.Generic; using System.Threading.Tasks; using AutoRest.Core; using AutoRest.Core.ClientModel; using AutoRest.Extensions.Azure; +using AutoRest.Extensions.Azure.Model; +using AutoRest.Ruby.Templates; using AutoRest.Ruby.Azure.TemplateModels; using AutoRest.Ruby.Azure.Templates; +using Newtonsoft.Json; using AutoRest.Ruby.TemplateModels; -using AutoRest.Ruby.Templates; namespace AutoRest.Ruby.Azure { @@ -19,12 +23,17 @@ namespace AutoRest.Ruby.Azure /// public class AzureRubyCodeGenerator : RubyCodeGenerator { + + // List of models with paging extensions. + private IList pageModels; + /// /// Initializes a new instance of the class AzureRubyCodeGenerator. /// /// The settings. public AzureRubyCodeGenerator(Settings settings) : base(settings) { + pageModels = new List(); } /// @@ -61,6 +70,79 @@ public override void NormalizeClientModel(ServiceClient serviceClient) AzureExtensions.NormalizeAzureClientModel(serviceClient, Settings, CodeNamer); CorrectFilterParameters(serviceClient); base.NormalizeClientModel(serviceClient); + AddRubyPageableMethod(serviceClient); + ApplyPagination(serviceClient); + } + + /// + /// Adds method to use for autopagination. + /// + /// The service client. + private void AddRubyPageableMethod(ServiceClient serviceClient) + { + if (serviceClient == null) + { + throw new ArgumentNullException(nameof(serviceClient)); + } + + for (int i = 0; i < serviceClient.Methods.Count; i++) + { + Method method = serviceClient.Methods[i]; + if (method.Extensions.ContainsKey(AzureExtensions.PageableExtension)) + { + PageableExtension pageableExtension = JsonConvert.DeserializeObject(method.Extensions[AzureExtensions.PageableExtension].ToString()); + if (pageableExtension != null && !method.Extensions.ContainsKey("nextLinkMethod") && !string.IsNullOrWhiteSpace(pageableExtension.NextLinkName)) + { + serviceClient.Methods.Insert(i, (Method)method.Clone()); + if (serviceClient.Methods[i].Extensions.ContainsKey("nextMethodName")) + { + serviceClient.Methods[i].Extensions["nextMethodName"] = CodeNamer.GetMethodName((string)method.Extensions["nextMethodName"]); + } + i++; + } + serviceClient.Methods[i].Extensions.Remove(AzureExtensions.PageableExtension); + } + } + } + + /// + /// Changes paginated method signatures to return Page type. + /// + /// The service client. + private void ApplyPagination(ServiceClient serviceClient) + { + if (serviceClient == null) + { + throw new ArgumentNullException(nameof(serviceClient)); + } + + foreach (Method method in serviceClient.Methods.Where(m => m.Extensions.ContainsKey(AzureExtensions.PageableExtension))) + { + string nextLinkName = null; + var ext = method.Extensions[AzureExtensions.PageableExtension] as Newtonsoft.Json.Linq.JContainer; + if (ext == null) + { + continue; + } + nextLinkName = CodeNamer.GetPropertyName((string)ext["nextLinkName"]); + string itemName = CodeNamer.GetPropertyName((string)ext["itemName"] ?? "value"); + foreach (var responseStatus in method.Responses.Where(r => r.Value.Body is CompositeType).Select(s => s.Key).ToArray()) + { + CompositeType compositeType = (CompositeType)method.Responses[responseStatus].Body; + SequenceType sequenceType = compositeType.Properties.Select(p => p.Type).FirstOrDefault(t => t is SequenceType) as SequenceType; + + // if the type is a wrapper over page-able response + if (sequenceType != null) + { + compositeType.Extensions[AzureExtensions.PageableExtension] = true; + PageTemplateModel pageTemplateModel = new PageTemplateModel(compositeType, serviceClient.ModelTypes, nextLinkName, itemName); + if (!pageModels.Contains(pageTemplateModel)) + { + pageModels.Add(pageTemplateModel); + } + } + } + } } /// @@ -84,16 +166,17 @@ public static void CorrectFilterParameters(ServiceClient serviceClient) } /// - /// Generates C# code for service client. + /// Generates Ruby code for Azure service client. /// /// The service client. /// Async tasks which generates SDK files. public override async Task Generate(ServiceClient serviceClient) { + var serviceClientTemplateModel = new AzureServiceClientTemplateModel(serviceClient); // Service client var serviceClientTemplate = new ServiceClientTemplate { - Model = new AzureServiceClientTemplateModel(serviceClient), + Model = serviceClientTemplateModel }; await Write(serviceClientTemplate, Path.Combine(sdkPath, RubyCodeNamer.UnderscoreCase(serviceClient.Name) + ImplementationFileExtension)); @@ -109,7 +192,7 @@ public override async Task Generate(ServiceClient serviceClient) } // Models - foreach (var model in serviceClient.ModelTypes) + foreach (var model in serviceClientTemplateModel.ModelTypes) { if ((model.Extensions.ContainsKey(AzureExtensions.ExternalExtension) && (bool)model.Extensions[AzureExtensions.ExternalExtension]) @@ -122,9 +205,17 @@ public override async Task Generate(ServiceClient serviceClient) { Model = new AzureModelTemplateModel(model, serviceClient.ModelTypes), }; - await Write(modelTemplate, Path.Combine(modelsPath, RubyCodeNamer.UnderscoreCase(model.Name) + ImplementationFileExtension)); } + // Paged Models + foreach (var pageModel in pageModels) + { + var pageTemplate = new PageModelTemplate + { + Model = pageModel + }; + await Write(pageTemplate, Path.Combine(modelsPath, RubyCodeNamer.UnderscoreCase(pageModel.Name) + ImplementationFileExtension)); + } // Enums foreach (var enumType in serviceClient.EnumTypes) diff --git a/src/generator/AutoRest.Ruby.Azure/GlobalSuppressions.cs b/src/generator/AutoRest.Ruby.Azure/GlobalSuppressions.cs index a7471952014f6..671b7c6bf1822 100644 --- a/src/generator/AutoRest.Ruby.Azure/GlobalSuppressions.cs +++ b/src/generator/AutoRest.Ruby.Azure/GlobalSuppressions.cs @@ -10,6 +10,7 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "AutoRest.Ruby.Azure.Templates")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "AutoRest.Core.Utilities.IndentedStringBuilder.AppendLine(System.String)", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodTemplateModel.#BuildUrl(System.String,System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "AutoRest.Core.Utilities.IndentedStringBuilder.AppendLine(System.String)", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodTemplateModel.#ResponseGeneration()")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "subscriptionid", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodTemplateModel.#BuildUrl(System.String,System.String)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "isRequired", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodTemplateModel.#DeserializePollingResponse(System.String,AutoRest.Core.ClientModel.IType,System.Boolean,System.String)")] @@ -23,15 +24,19 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "httpresponse", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodTemplateModel.#InitializeResponseBody")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "Util", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodTemplateModel.#BuildUrl(System.String,System.String)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "urlencode", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodTemplateModel.#BuildUrl(System.String,System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "firstpage", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodTemplateModel.#ResponseGeneration()")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "getallitems", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodTemplateModel.#ResponseGeneration()")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "requestheaders", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodTemplateModel.#SetDefaultHeaders")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodTemplateModel.#FaradeyMiddlewares")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodGroupTemplateModel.#Includes")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureModelTemplateModel.#Includes")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureServiceClientTemplateModel.#Includes")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "AutoRest.Ruby.Azure.PageTemplateModel.#Includes")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureRequirementsTemplateModel.#ExcludeModel(AutoRest.Core.ClientModel.CompositeType)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureRubyCodeGenerator.#CorrectFilterParameters(AutoRest.Core.ClientModel.ServiceClient)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureMethodTemplateModel.#ClassNamespaces")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "AutoRest.Ruby.Azure.AzureModelTemplateModel.#ClassNamespaces")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "AutoRest.Ruby.Azure.PageTemplateModel.#ClassNamespaces")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1704:Microsoft.Naming")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1303:Microsoft.Globalization")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1305:Microsoft.Globalization")] diff --git a/src/generator/AutoRest.Ruby.Azure/TemplateModels/AzureMethodTemplateModel.cs b/src/generator/AutoRest.Ruby.Azure/TemplateModels/AzureMethodTemplateModel.cs index 36903b4a9b039..bfec27ea09620 100644 --- a/src/generator/AutoRest.Ruby.Azure/TemplateModels/AzureMethodTemplateModel.cs +++ b/src/generator/AutoRest.Ruby.Azure/TemplateModels/AzureMethodTemplateModel.cs @@ -6,11 +6,14 @@ using System.Globalization; using System.Linq; using System.Net; +using System.Text; using AutoRest.Core.ClientModel; using AutoRest.Extensions.Azure; +using AutoRest.Extensions.Azure.Model; using AutoRest.Ruby.Azure.Properties; using AutoRest.Ruby.TemplateModels; using IndentedStringBuilder = AutoRest.Core.Utilities.IndentedStringBuilder; +using Newtonsoft.Json; namespace AutoRest.Ruby.Azure.TemplateModels { @@ -51,6 +54,87 @@ public bool IsLongRunningOperation get { return Extensions.ContainsKey(AzureExtensions.LongRunningExtension); } } + /// + /// Returns true if method has x-ms-pageable extension. + /// + public bool IsPageable + { + get { return Extensions.ContainsKey(AzureExtensions.PageableExtension); } + } + + /// + /// Returns invocation string for next method async. + /// + public string InvokeNextMethodAsync() + { + StringBuilder builder = new StringBuilder(); + string nextMethodName; + PageableExtension pageableExtension = JsonConvert.DeserializeObject(Extensions[AzureExtensions.PageableExtension].ToString()); + + Method nextMethod = null; + if (pageableExtension != null && !string.IsNullOrEmpty(pageableExtension.OperationName)) + { + nextMethod = ServiceClient.Methods.FirstOrDefault(m => + pageableExtension.OperationName.Equals(m.SerializedName, StringComparison.OrdinalIgnoreCase)); + nextMethodName = nextMethod.Name; + } + else + { + nextMethodName = (string)Extensions["nextMethodName"]; + nextMethod = ServiceClient.Methods.Where(m => m.Name == nextMethodName).FirstOrDefault(); + } + + IEnumerable origMethodGroupedParameters = Parameters.Where(p => p.Name.Contains(Name)); + if (origMethodGroupedParameters.Count() > 0) + { + foreach (Parameter param in nextMethod.Parameters) + { + if (param.Name.Contains(nextMethod.Name) && (param.Name.Length > nextMethod.Name.Length)) //parameter that contains the method name + postfix, it's a grouped param + { + //assigning grouped parameter passed to the lazy method, to the parameter used in the invocation to the next method + string argumentName = param.Name.Replace(nextMethodName, Name); + builder.AppendLine(string.Format(CultureInfo.InvariantCulture, "{0} = {1}", param.Name, argumentName)); + } + } + } + + IList headerParams = nextMethod.Parameters.Where(p => (p.Location == ParameterLocation.Header || p.Location == ParameterLocation.None) && !p.IsConstant && p.ClientProperty == null).Select(p => p.Name).ToList(); + headerParams.Add("custom_headers"); + string nextMethodParamaterInvocation = string.Join(", ", headerParams); + + builder.AppendLine(string.Format(CultureInfo.InvariantCulture, "{0}_async(next_link, {1})", nextMethodName, nextMethodParamaterInvocation)); + return builder.ToString(); + } + + /// + /// Returns generated response or body of the auto-paginated method. + /// + public override string ResponseGeneration() + { + + IndentedStringBuilder builder = new IndentedStringBuilder(); + if (ReturnType.Body != null) + { + if (ReturnType.Body is CompositeType) + { + CompositeType compositeType = (CompositeType)ReturnType.Body; + if (compositeType.Extensions.ContainsKey(AzureExtensions.PageableExtension)) + { + bool isNextLinkMethod = this.Extensions.ContainsKey("nextLinkMethod") && (bool)this.Extensions["nextLinkMethod"]; + bool isPageable = (bool)compositeType.Extensions[AzureExtensions.PageableExtension]; + if (isPageable && !isNextLinkMethod) + { + builder.AppendLine("first_page = {0}_as_lazy({1})", Name, MethodParameterInvocation); + builder.AppendLine("first_page.get_all_items"); + return builder.ToString(); + } + } + } + } + return base.ResponseGeneration(); + + } + /// /// Gets the Get method model. /// @@ -180,5 +264,21 @@ public override string OperationExceptionTypeString return base.OperationExceptionTypeString; } } + + /// + /// Gets the type for operation result. + /// + public override string OperationReturnTypeString + { + get + { + if (Extensions.ContainsKey("nextMethodName") && !Extensions.ContainsKey(AzureExtensions.PageableExtension)) + { + SequenceType sequenceType = ((CompositeType)ReturnType.Body).Properties.Select(p => p.Type).FirstOrDefault(t => t is SequenceType) as SequenceType; + return string.Format(CultureInfo.InvariantCulture, "Array<{0}>", sequenceType.ElementType.Name); + } + return base.OperationReturnTypeString; + } + } } } \ No newline at end of file diff --git a/src/generator/AutoRest.Ruby.Azure/TemplateModels/PageTemplateModel.cs b/src/generator/AutoRest.Ruby.Azure/TemplateModels/PageTemplateModel.cs new file mode 100644 index 0000000000000..a4c3caf59a1ab --- /dev/null +++ b/src/generator/AutoRest.Ruby.Azure/TemplateModels/PageTemplateModel.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using AutoRest.Core.ClientModel; +using AutoRest.Ruby; +using AutoRest.Ruby.Azure.TemplateModels; +using AutoRest.Extensions; + +namespace AutoRest.Ruby.Azure +{ + public class PageTemplateModel : AzureModelTemplateModel + { + public PageTemplateModel(CompositeType source, ISet allTypes, string nextLinkName, string itemName) + : base(source, allTypes) + { + this.NextLinkName = nextLinkName; + this.ItemName = itemName; + } + + public string NextLinkName { get; private set; } + + public string ItemName { get; private set; } + + public CompositeType ItemType + { + get + { + if (Properties == null) + { + return null; + } + Property property = Properties.FirstOrDefault(p => p.Type is SequenceType); + if (property != null) + { + return ((SequenceType)property.Type).ElementType as CompositeType; + } + else + { + return null; + } + } + } + } +} \ No newline at end of file diff --git a/src/generator/AutoRest.Ruby.Azure/Templates/AzureMethodTemplate.cshtml b/src/generator/AutoRest.Ruby.Azure/Templates/AzureMethodTemplate.cshtml index 93c4546e50f09..202359673844a 100644 --- a/src/generator/AutoRest.Ruby.Azure/Templates/AzureMethodTemplate.cshtml +++ b/src/generator/AutoRest.Ruby.Azure/Templates/AzureMethodTemplate.cshtml @@ -1,19 +1,18 @@ @using System -@using AutoRest.Core.ClientModel @using AutoRest.Ruby @using AutoRest.Ruby.TemplateModels @using AutoRest.Ruby.Templates @inherits AutoRest.Core.Template -@if (!Model.IsLongRunningOperation) +@if (!Model.IsLongRunningOperation && !Model.IsPageable) { @:@(Include(new MethodTemplate(), (MethodTemplateModel)Model)) } -else if (Model.HttpMethod == HttpMethod.Post || Model.HttpMethod == HttpMethod.Delete) +else if (!Model.IsLongRunningOperation && Model.IsPageable) { # -@if (!String.IsNullOrEmpty(Model.Summary)) +@if(!String.IsNullOrEmpty(Model.Summary)) { @WrapComment("# ", Model.Summary)@: @:# @@ -27,29 +26,19 @@ else if (Model.HttpMethod == HttpMethod.Post || Model.HttpMethod == HttpMethod.D { @:@WrapComment("# ", string.Format("@param {0} {1}{2}", parameter.Name, parameter.Type.GetYardDocumentation(), parameter.Documentation)) } -@WrapComment("# ", "@return [Concurrent::Promise] promise which provides async access to http response.") +@WrapComment("# ", string.Format("@param custom_headers [{0}] A hash of custom headers that will be added to the HTTP request.", "Hash{String => String}")) # -def @(Model.Name)(@(Model.MethodParameterDeclaration)) - # Send request - promise = begin_@(Model.Name)_async(@(Model.MethodParameterInvocation)) - @EmptyLine - - promise = promise.then do |response| - # Defining deserialization method. - deserialize_method = lambda do |parsed_response| - @if(Model.ReturnType.Body != null) - { - @:@(Model.DeserializePollingResponse("parsed_response", Model.ReturnType.Body)) - } +@WrapComment("# ", string.Format("@return [{0}] which provide lazy access to pages of the response.", Model.ReturnType)) +# +def @(Model.Name)_as_lazy(@(Model.MethodParameterDeclaration)) + response = @(Model.Name)_async(@(Model.MethodParameterInvocation)).value! + unless response.nil? + page = response.body + page.next_method = Proc.new do |next_link| + @(Model.InvokeNextMethodAsync()) end - - @EmptyLine - # Waiting for response. - @(Model.ClientReference).get_long_running_operation_result(response, deserialize_method) + page end - - @EmptyLine - promise end } @@ -67,10 +56,11 @@ else @WrapComment("# ", Model.Description)@: @:# } -@foreach (var parameter in Model.Parameters) +@foreach (var parameter in Model.LocalParameters) { @:@WrapComment("# ", string.Format("@param {0} {1}{2}", parameter.Name, parameter.Type.GetYardDocumentation(), parameter.Documentation)) } +@WrapComment("# ", string.Format("@param custom_headers [{0}] A hash of custom headers that will be added to the HTTP request.", "Hash{String => String}")) # @WrapComment("# ", "@return [Concurrent::Promise] promise which provides async access to http response.") # @@ -82,7 +72,7 @@ def @(Model.Name)(@(Model.MethodParameterDeclaration)) promise = promise.then do |response| # Defining deserialization method. deserialize_method = lambda do |parsed_response| - @if(Model.ReturnType.Body != null) + @if (Model.ReturnType.Body != null) { @:@(Model.DeserializePollingResponse("parsed_response", Model.ReturnType.Body)) } diff --git a/src/generator/AutoRest.Ruby.Azure/Templates/PageModelTemplate.cshtml b/src/generator/AutoRest.Ruby.Azure/Templates/PageModelTemplate.cshtml new file mode 100644 index 0000000000000..15fa586437cca --- /dev/null +++ b/src/generator/AutoRest.Ruby.Azure/Templates/PageModelTemplate.cshtml @@ -0,0 +1,97 @@ +@using System.Linq +@using System.Collections.Generic +@using AutoRest.Core.ClientModel +@using AutoRest.Ruby +@using AutoRest.Ruby.Azure +@using AutoRest.Ruby.Azure.TemplateModels; +@using AutoRest.Ruby.Azure.Templates +@using AutoRest.Core.Utilities +@inherits AutoRest.Core.Template +# encoding: utf-8 +@Header("# ") +@EmptyLine +module @(Settings.Namespace) + module Models + # + @WrapComment("# ", string.IsNullOrEmpty(Model.Documentation) ? "Model object." : Model.Documentation) + # + class @Model.Name@(Model.GetBaseTypeName()) + @if (Model.Includes.Any()) + { + @EmptyLine + foreach (var include in Model.Includes) + { + @:include @include + } + @EmptyLine + } + + @if (Model.IsPolymorphic && Model.BaseModelType == null) + { + @:@@@@discriminatorMap = Hash.new + @:@@@@discriminatorMap["@Model.SerializedName"] = "@Model.Name" + foreach (var derivedType in Model.DerivedTypes) + { + @:@@@@discriminatorMap["@derivedType.SerializedName"] = "@derivedType.Name" + } + } + + @foreach (var property in Model.PropertyTemplateModels) + { + @:@WrapComment("# ", string.Format("@return {0}{1}", property.Type.GetYardDocumentation(), property.Documentation)) + // @:@(property.IsReadOnly ? "attr_reader" : "attr_accessor") :@property.Name + @:attr_accessor :@property.Name + @EmptyLine + @: + } + + # return [Proc] with next page method call. + attr_accessor :next_method + + @EmptyLine + + # + # Gets the rest of the items for the request, enabling auto-pagination. + # + @WrapComment("# ", string.Format("@return [Array<{0}>] operation results.", Model.ItemType)) + # + def get_all_items + items = @@@Model.ItemName + page = self + while page.@(Model.NextLinkName) != nil do + page = page.get_next_page + items.concat(page.@Model.ItemName) + end + items + end + + @EmptyLine + + # + # Gets the next page of results. + # + @WrapComment("# ", string.Format("@return [{0}] with next page content.", Model.Name)) + # + def get_next_page + @{ + @:response = @@next_method.call(@@@(Model.NextLinkName)).value! unless @@next_method.nil? + @:unless response.nil? + @:@@@(Model.NextLinkName) = response.body.@(Model.NextLinkName) + @:@@@(Model.ItemName) = response.body.@(Model.ItemName) + @:self + @:end + } + end + + @EmptyLine + # + @WrapComment("# ", string.Format("Mapper for {0} class as Ruby Hash.", Model.Name)) + # This will be used for serialization/deserialization. + # + def self.mapper() + @(Model.ConstructModelMapper()) + end + + end + end +end \ No newline at end of file diff --git a/src/generator/AutoRest.Ruby/TemplateModels/MethodTemplateModel.cs b/src/generator/AutoRest.Ruby/TemplateModels/MethodTemplateModel.cs index 43e2ce226e049..2a62342f9f4f9 100644 --- a/src/generator/AutoRest.Ruby/TemplateModels/MethodTemplateModel.cs +++ b/src/generator/AutoRest.Ruby/TemplateModels/MethodTemplateModel.cs @@ -368,6 +368,17 @@ public bool UrlWithPath } } + /// + /// Gets the type for operation result. + /// + public virtual string OperationReturnTypeString + { + get + { + return ReturnType.Body.Name.ToString(); + } + } + /// /// Creates a code in form of string which deserializes given input variable of given type. /// @@ -499,6 +510,24 @@ public virtual string BuildInputParameterMappings() return builder.ToString(); } + /// + /// Generates response or body of method + /// + public virtual string ResponseGeneration() + { + IndentedStringBuilder builder = new IndentedStringBuilder(""); + builder.AppendLine("response = {0}_async({1}).value!", Name, MethodParameterInvocation); + if (ReturnType.Body != null) + { + builder.AppendLine("response.body unless response.nil?"); + } + else + { + builder.AppendLine("nil"); + } + return builder.ToString(); + } + /// /// Gets the formatted status code. /// @@ -617,7 +646,7 @@ public string GetDeserializationString(IType type, string valueReference = "resu { builder.AppendLine("{1} = @client.deserialize(result_mapper, {0}, '{1}')", responseVariable, valueReference); } - + return builder.ToString(); } @@ -635,7 +664,7 @@ private static string BuildNullCheckExpression(ParameterTransformation transform if (transformation.ParameterMappings.Count == 1) { return string.Format(CultureInfo.InvariantCulture, - "{0}.nil?",transformation.ParameterMappings[0].InputParameter.Name); + "{0}.nil?", transformation.ParameterMappings[0].InputParameter.Name); } else { diff --git a/src/generator/AutoRest.Ruby/TemplateModels/ServiceClientTemplateModel.cs b/src/generator/AutoRest.Ruby/TemplateModels/ServiceClientTemplateModel.cs index 738194ec9a097..6460a98c05273 100644 --- a/src/generator/AutoRest.Ruby/TemplateModels/ServiceClientTemplateModel.cs +++ b/src/generator/AutoRest.Ruby/TemplateModels/ServiceClientTemplateModel.cs @@ -34,7 +34,7 @@ public ServiceClientTemplateModel(ServiceClient serviceClient) public bool HasModelTypes { get; private set; } /// - /// Gets and sets the model template models. + /// Gets and sets the method template models. /// public List MethodTemplateModels { get; set; } diff --git a/src/generator/AutoRest.Ruby/Templates/MethodTemplate.cshtml b/src/generator/AutoRest.Ruby/Templates/MethodTemplate.cshtml index cd698eaec902c..0f5397e2c581f 100644 --- a/src/generator/AutoRest.Ruby/Templates/MethodTemplate.cshtml +++ b/src/generator/AutoRest.Ruby/Templates/MethodTemplate.cshtml @@ -1,7 +1,7 @@ @using System @using System.Linq; @using AutoRest.Core.ClientModel -@using AutoRest.Ruby +@using AutoRest.Ruby.TemplateModels @inherits AutoRest.Core.Template # @@ -23,22 +23,13 @@ # @if (Model.ReturnType.Body != null) { -@WrapComment("# ", string.Format("@return [{0}] operation results.", Model.ReturnType.Body.Name))@: +@WrapComment("# ", string.Format("@return [{0}] operation results.", Model.OperationReturnTypeString))@: } # def @(Model.Name)(@(Model.MethodParameterDeclaration)) - response = @(Model.Name)_async(@(Model.MethodParameterInvocation)).value! -@if (Model.ReturnType.Body != null) -{ - @:response.body unless response.nil? -} -else -{ - @:nil -} + @Model.ResponseGeneration() end - @EmptyLine # @if (!String.IsNullOrEmpty(Model.Summary))